Доступ к состоянию Redux в создателе действия?

Скажем, у меня есть следующее:

export const SOME_ACTION = 'SOME_ACTION';
export function someAction() {
  return {
    type: SOME_ACTION,
  }
}

И в этом создателе действия я хочу получить доступ к глобальному состоянию хранилища (все редукторы). Лучше ли это сделать:

import store from '../store';

export const SOME_ACTION = 'SOME_ACTION';
export function someAction() {
  return {
    type: SOME_ACTION,
    items: store.getState().otherReducer.items,
  }
}

или это:

export const SOME_ACTION = 'SOME_ACTION';
export function someAction() {
  return (dispatch, getState) => {
    const {items} = getState().otherReducer;

    dispatch(anotherAction(items));
  }
}

Ответ 1

Существуют разные мнения о том, является ли доступ к состоянию в действии создателями хорошей идеей:

  • Создатель Redux Дэн Абрамов считает, что его следует ограничить: "Несколько случаев, когда я считаю приемлемым, проверять кэшированные данные перед тем, как сделать запрос, или проверить, аутентифицированы ли вы (другими словами, выполнять условную диспетчеризацию). Я думаю, что передача данных, таких как state.something.items, в создателя действий определенно является анти-паттерном и не рекомендуется, потому что затеняет историю изменений: если есть ошибка и items неверны, это Трудно отследить, откуда взялись эти неверные значения, потому что они уже являются частью действия, а не вычисляются напрямую редуктором в ответ на действие. Так что делайте это осторожно. "
  • Текущий сопровождающий Redux Марк Эриксон говорит, что это прекрасно, и даже поощряет использовать getState в громадных количествах - вот почему он существует. Он рассказывает о плюсах и минусах доступа к состоянию в создателях действий в своем блоге Idiomatic Redux: мысли о Thunks, Sagas, Abstraction и Reusability.

Если вы обнаружите, что вам это нужно, оба предложенных вами подхода подойдут. Первый подход не требует промежуточного программного обеспечения:

import store from '../store';

export const SOME_ACTION = 'SOME_ACTION';
export function someAction() {
  return {
    type: SOME_ACTION,
    items: store.getState().otherReducer.items,
  }
}

Тем не менее, вы можете видеть, что он полагается на store, представляющий собой одноэлемент, экспортированный из какого-либо модуля. Мы не рекомендуем, потому что это усложняет добавление рендеринга сервера в ваше приложение, потому что в большинстве случаев на сервере вы хотите иметь отдельное хранилище для каждого запроса. Поэтому, хотя технически этот подход работает, мы не рекомендуем экспортировать магазин из модуля.

Вот почему мы рекомендуем второй подход:

export const SOME_ACTION = 'SOME_ACTION';
export function someAction() {
  return (dispatch, getState) => {
    const {items} = getState().otherReducer;

    dispatch(anotherAction(items));
  }
}

Для этого потребуется использовать промежуточное ПО Redux Thunk, но оно отлично работает как на клиенте, так и на сервере. Вы можете узнать больше о Redux Thunk и о том, почему это необходимо в этом случае здесь.

В идеале, ваши действия не должны быть "жирными" и содержать как можно меньше информации, но вы должны свободно делать то, что лучше всего подходит для вас в вашем собственном приложении. Часто задаваемые вопросы по Redux содержат информацию о логике разделения между создателями действий и редукторами и о случаях , когда может быть полезно использовать getState в создателе действий.

Ответ 2

Когда ваш сценарий прост, вы можете использовать

import store from '../store';

export const SOME_ACTION = 'SOME_ACTION';
export function someAction() {
  return {
    type: SOME_ACTION,
    items: store.getState().otherReducer.items,
  }
}

Но иногда ваш action creator должен запускать несколько действий

например async-запрос, так что вам нужно REQUEST_LOAD REQUEST_LOAD_SUCCESS REQUEST_LOAD_FAIL действия

export const [REQUEST_LOAD, REQUEST_LOAD_SUCCESS, REQUEST_LOAD_FAIL] = [`REQUEST_LOAD`
    `REQUEST_LOAD_SUCCESS`
    `REQUEST_LOAD_FAIL`
]
export function someAction() {
    return (dispatch, getState) => {
        const {
            items
        } = getState().otherReducer;
        dispatch({
            type: REQUEST_LOAD,
            loading: true
        });
        $.ajax('url', {
            success: (data) => {
                dispatch({
                    type: REQUEST_LOAD_SUCCESS,
                    loading: false,
                    data: data
                });
            },
            error: (error) => {
                dispatch({
                    type: REQUEST_LOAD_FAIL,
                    loading: false,
                    error: error
                });
            }
        })
    }
}

Примечание: вам нужно redux-thunk вернуть функцию в создателе действия

Ответ 3

Я хотел бы отметить, что читать в магазине не так уж плохо, поэтому было бы гораздо удобнее решить, что должно быть сделано на основе магазина, чем передать все компоненту, а затем как параметр функции. Я полностью согласен с Дэном в том, что гораздо лучше не использовать хранилище в качестве одиночного, если вы на 100% не уверены, что будете использовать только для рендеринга на стороне клиента (в противном случае могут возникнуть проблемы с отслеживанием ошибок).

Недавно я создал библиотеку, чтобы иметь дело с многословием редукса, и я думаю, что это хорошая идея поместить все в промежуточное ПО, поэтому у вас есть все как инъекция зависимости.

Итак, ваш пример будет выглядеть следующим образом:

import { createSyncTile } from 'redux-tiles';

const someTile = createSyncTile({
  type: ['some', 'tile'],
  fn: ({ params, selectors, getState }) => {
    return {
      data: params.data,
      items: selectors.another.tile(getState())
    };
  },
});

Однако, как вы можете видеть, мы действительно не модифицируем данные здесь, поэтому есть хороший шанс, что мы можем просто использовать этот селектор в другом месте, чтобы объединить его где-то еще.

Ответ 4

Я согласен с @Bloomca. Передача нужного значения из хранилища в диспетчерскую функцию в качестве аргумента выглядит проще, чем экспорт хранилища. Я сделал пример здесь:

import React from "react";
import {connect} from "react-redux";
import * as actions from '../actions';

class App extends React.Component {

  handleClick(){
    const data = this.props.someStateObject.data;
    this.props.someDispatchFunction(data);
  }

  render(){
    return (
      <div>       
      <div onClick={ this.handleClick.bind(this)}>Click Me!</div>      
      </div>
    );
  }
}


const mapStateToProps = (state) => {
  return { someStateObject: state.someStateObject };
};

const mapDispatchToProps = (dispatch) => {
  return {
    someDispatchFunction:(data) => { dispatch(actions.someDispatchFunction(data))},

  };
}


export default connect(mapStateToProps, mapDispatchToProps)(App);

Ответ 5

Представляем альтернативный способ решения этой проблемы. Это может быть лучше или хуже, чем решение Dan, в зависимости от вашего приложения.

Вы можете получить состояние от редукторов к действиям, разделив действие на две отдельные функции: сначала запросите данные, затем выполните действие с данными. Вы можете сделать это, используя redux-loop.

Сначала "пожалуйста, запросите данные"

export const SOME_ACTION = 'SOME_ACTION';
export function someAction() {
    return {
        type: SOME_ACTION,
    }
}

В редукторе перехватите запрос и предоставьте данные для действия второго этапа, используя redux-loop.

import { loop, Cmd } from 'redux-loop';
const initialState = { data: '' }
export default (state=initialState, action) => {
    switch(action.type) {
        case SOME_ACTION: {
            return loop(state, Cmd.action(anotherAction(state.data))
        }
    }
}

Имея данные в руках, делайте то, что вы изначально хотели

export const ANOTHER_ACTION = 'ANOTHER_ACTION';
export function anotherAction(data) {
    return {
        type: ANOTHER_ACTION,
        payload: data,
    }
}

Надеюсь, это кому-нибудь поможет.

Ответ 6

Я знаю, что опоздал на вечеринку здесь, но я пришел сюда для того, чтобы высказать свое мнение о своем желании использовать государство в действиях, а затем сформировал свое собственное, когда понял, что считаю правильным поведением.

Это где селектор имеет наибольшее значение для меня. Ваш компонент, который выдает этот запрос, должен быть уведомлен о том, что пора выпустить его через выбор.

export const SOME_ACTION = 'SOME_ACTION';
export function someAction(items) {
  return (dispatch) => {
    dispatch(anotherAction(items));
  }
}

Это может показаться утечкой абстракций, но вашему компоненту явно необходимо отправить сообщение, а полезная нагрузка сообщения должна содержать соответствующее состояние. К сожалению, у вашего вопроса нет конкретного примера, потому что мы могли бы проработать "лучшую модель" селекторов и действий таким образом.