Доступ к состоянию 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));
}
}
4 ответа
Существуют разные мнения о том, является ли доступ к состоянию в действии создателями хорошей идеей. В тех немногих случаях использования, которые я считаю приемлемыми, можно проверить кэшированные данные перед выполнением запроса или проверить, прошли ли вы проверку подлинности (другими словами, выполнить условную диспетчеризацию). Я думаю, что передача данных, таких как state.something.items
в действии создатель определенно является анти-паттерном и не рекомендуется, потому что он затеняет историю изменений: если есть ошибка и items
неверны, трудно отследить, откуда эти неправильные значения, потому что они уже являются частью действия, а не вычисляются напрямую редуктором в ответ на действие. Так что делай это с осторожностью. (Дальнейшее обсуждение плюсов и минусов доступа к состоянию в создателях действий см. В блоге 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 FAQ есть информация о том, как разделить логику между создателями действий и редукторами, и о том, когда это может быть полезно для использования. getState
в действии создателя.
Когда ваш сценарий прост, вы можете использовать
import store from '../store';
export const SOME_ACTION = 'SOME_ACTION';
export function someAction() {
return {
type: SOME_ACTION,
items: store.getState().otherReducer.items,
}
}
Но иногда ваш action creator
нужно вызвать несколько действий
например асинхронный запрос, так что вам нужно 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, чтобы вернуть функцию в создатель действия
Я согласен с @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);
Я хотел бы отметить, что читать из магазина не так уж и плохо - может быть, гораздо удобнее решить, что делать на основе хранилища, чем передать все компоненту, а затем в качестве параметра функция. Я полностью согласен с Дэном в том, что гораздо лучше не использовать хранилище в качестве синглтона, если только вы не уверены на 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())
};
},
});
Однако, как вы можете видеть, мы на самом деле не изменяем данные здесь, поэтому есть большая вероятность, что мы можем просто использовать этот селектор в другом месте, чтобы объединить его где-то еще.
Представляем альтернативный способ решения этой проблемы. Это может быть лучше или хуже, чем решение Дэна, в зависимости от вашего приложения.
Вы можете получить состояние от редукторов к действиям, разделив действие на две отдельные функции: сначала запросите данные, затем выполните действие с данными. Вы можете сделать это с помощью 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,
}
}
Надеюсь, это кому-нибудь поможет.
Я знаю, что опаздываю на вечеринку здесь, но я пришел сюда, чтобы узнать мнение о моем собственном желании использовать состояние в действиях, а затем сформировал свое собственное, когда понял, что я считаю правильным поведением.
Именно здесь селектор имеет для меня наибольший смысл. Компоненту, который выдает этот запрос, следует сообщить, что пора его выдать путем выбора.
export const SOME_ACTION = 'SOME_ACTION';
export function someAction(items) {
return (dispatch) => {
dispatch(anotherAction(items));
}
}
Это может показаться утечкой абстракций, но вашему компоненту явно необходимо отправить сообщение, а полезная нагрузка сообщения должна содержать соответствующее состояние. К сожалению, в вашем вопросе нет конкретного примера, потому что таким образом мы могли бы проработать "лучшую модель" селекторов и действий.
Я хотел бы предложить еще один вариант, который я считаю наиболее чистым, но он требует react-redux
или что-то похожее - также я использую несколько других необычных функций на этом пути:
// actions.js
export const someAction = (items) => ({
type: 'SOME_ACTION',
payload: {items},
});
// Component.jsx
import {connect} from "react-redux";
const Component = ({boundSomeAction}) => (<div
onClick={boundSomeAction}
/>);
const mapState = ({otherReducer: {items}}) => ({
items,
});
const mapDispatch = (dispatch) => bindActionCreators({
someAction,
}, dispatch);
const mergeProps = (mappedState, mappedDispatches) => {
// you can only use what gets returned here, so you dont have access to `items` and
// `someAction` anymore
return {
boundSomeAction: () => mappedDispatches.someAction(mappedState.items),
}
});
export const ConnectedComponent = connect(mapState, mapDispatch, mergeProps)(Component);
// (with other mapped state or dispatches) Component.jsx
import {connect} from "react-redux";
const Component = ({boundSomeAction, otherAction, otherMappedState}) => (<div
onClick={boundSomeAction}
onSomeOtherEvent={otherAction}
>
{JSON.stringify(otherMappedState)}
</div>);
const mapState = ({otherReducer: {items}, otherMappedState}) => ({
items,
otherMappedState,
});
const mapDispatch = (dispatch) => bindActionCreators({
someAction,
otherAction,
}, dispatch);
const mergeProps = (mappedState, mappedDispatches) => {
const {items, ...remainingMappedState} = mappedState;
const {someAction, ...remainingMappedDispatch} = mappedDispatch;
// you can only use what gets returned here, so you dont have access to `items` and
// `someAction` anymore
return {
boundSomeAction: () => someAction(items),
...remainingMappedState,
...remainingMappedDispatch,
}
});
export const ConnectedComponent = connect(mapState, mapDispatch, mergeProps)(Component);
Если вы хотите повторно использовать это, вам нужно будет извлечь конкретный mapState
, mapDispatch
а также mergeProps
в функции для повторного использования в другом месте, но это делает зависимости совершенно ясными.
Я бы не получил доступ к состоянию в Action Creator. Я бы использовал mapStateToProps() и импортировал бы весь объект состояния и импортировал бы файл CombinedReducer (или
import * from './reducers';
) в компоненте, в который в конечном итоге собирается Action Creator. Затем используйте деструктуризацию в компоненте, чтобы использовать все, что вам нужно из свойства состояния. Если Action Creator передает состояние в Редуктор для данного ТИПА, вам не нужно упоминать состояние, потому что редуктор имеет доступ ко всему, что в настоящее время установлено в состоянии. Ваш пример ничего не обновляет. Я бы использовал Action Creator только для передачи состояния из его параметров.
В редукторе сделайте что-то вроде:
const state = this.state;
const apple = this.state.apples;
Если вам нужно выполнить действие с состоянием для ТИПА, на который вы ссылаетесь, сделайте это в редукторе.
Пожалуйста, поправьте меня, если я ошибаюсь!!!