在一个 action creator 中访问 Redux 状态?

371

假设我有以下内容:

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

在那个 action 创建函数中,我想要访问全局 store 状态(所有 reducers)。这样做更好吗:

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));
  }
}
8个回答

628

关于在操作创建者中访问状态是否是一个好主意,存在不同的观点:

  • Redux的创始人Dan Abramov认为应该有所限制:“我认为可以接受的用例很少,比如在发出请求之前检查缓存数据,或者检查是否已经验证身份(换句话说,进行条件分发)。我认为在操作创建器中传递数据,比如state.something.items,绝对是一种反模式,并且不鼓励这样做,因为它会掩盖变更历史:如果存在错误并且items不正确,则很难追踪这些不正确的值来自哪里,因为它们已经成为操作的一部分,而不是直接由reducer根据操作直接计算得出。所以请谨慎使用。”
  • 当前的Redux维护者Mark Erikson表示,在thunk中使用getState是可以的,甚至是鼓励的,这就是它存在的原因。他在博客文章Idiomatic Redux: Thoughts on Thunks, Sagas, Abstraction, and 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以及为什么在这种情况下它是必需的here
理想情况下,您的操作应该不是“臃肿”的,应尽可能包含较少的信息,但在您自己的应用程序中可以随意使用最好的方法。Redux FAQ提供了有关将逻辑分离在操作创建者和减速器之间以及在操作创建者中使用 getState 时可能有用的时间splitting logic between action creators and reducerstimes when it may be useful to use getState in an action creator的信息。

5
在一个组件中选择某些内容会触发一个PUT或POST请求的情况,具体取决于存储中是否有与该组件相关的数据。将PUT / POST选择的业务逻辑放在组件中而不是基于thunk的action creator中是否更好呢? - vicusbass
3
最佳实践是什么?我现在遇到了类似的问题,我在我的操作创建者中使用getState。在我的情况下,我使用它来确定表单值是否有未完成的更改(如果有,则我将分派一个显示对话框的操作)。 - Arne H. Bitubekk
51
在 action creator 中从 store 读取数据是可以的。我建议您使用 selector,这样您就不必依赖于精确的状态结构。 - Dan Abramov
2
我正在使用中间件将数据发送到Mixpanel。因此,我的操作中有一个元键。我需要从状态中传递不同的变量到Mixpanel。在操作创建者上设置它们似乎是一种反模式。处理这些用例的最佳方法是什么? - Aakash Sigdel
3
哦,天啊!我不知道redux-thunk的第二个参数是getState,我一直在想,谢谢你帮我解决了这个问题。 - Lai32290
显示剩余5条评论

48

当您的场景比较简单时,您可以使用

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_LOADREQUEST_LOAD_SUCCESSREQUEST_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
我可以问一下是否应该检查状态为“加载中”的状态,以便在第一个请求完成时不会发出另一个ajax请求吗? - JoeTidee
在这个例子中,已经完成了加载状态的分发。例如,如果您使用按钮执行此操作,则应在那里检查 loading === true 并禁用该按钮。 - croraf

5

我同意@Bloomca的说法。将所需的值从store传递到dispatch函数作为参数似乎比导出store更简单。我在这里提供了一个示例:

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);

这个方法非常合乎逻辑。 - Ahmet Şimşek
这是正确的做法,也是我的做法。你的 action-creator 不需要知道整个状态,只需知道与之相关的部分。 - Ash

3
我想指出,从存储中读取数据并不是很糟糕的事情-- 基于存储来决定应该执行什么操作可能比将所有内容传递到组件再作为函数参数更加方便。我完全同意Dan的观点,不要将存储用作单例,除非您100%确定只会在客户端渲染中使用(否则可能出现难以跟踪的错误)。
最近我创建了一个来处理redux的冗长性,我认为将所有内容置于中间件中是个好主意,这样就可以将其作为依赖注入进行处理。
因此,您的示例将如下所示:
import { createSyncTile } from 'redux-tiles';

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

然而,正如您所看到的,我们并没有在这里修改数据,因此有很大的可能性我们可以在其他地方使用这个选择器来将其组合在一起。


1

提出一个解决问题的替代方案。这可能比Dan的解决方案更好或更差,具体取决于您的应用程序。

通过将操作分为两个单独的函数:首先请求数据,其次对数据进行操作,您可以将状态从reducers传递到actions中。您可以使用redux-loop来实现这一点。

首先“友好地请求数据”

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

在reducer中,使用redux-loop拦截请求并将数据提供给第二阶段的action。
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,
    }
}

希望这能帮到某些人。

0

我想提出另一种我认为最干净的替代方案,但它需要使用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);

如果您想要重用此内容,则需要将特定的mapStatemapDispatchmergeProps提取到函数中,以便在其他地方重复使用,但这可以使依赖关系变得完全清晰。

0

我知道我来晚了,但我来这里是为了听取关于在操作中使用状态的意见,后来我自己形成了自己的看法,当我意识到我认为正确的行为时。

这就是我认为选择器最有意义的地方。您发出此请求的组件应通过选择告知其何时发出请求。

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

虽然可能感觉像是泄漏抽象,但你的组件显然需要发送消息,消息负载应包含相关状态。不幸的是,你的问题没有具体的示例,因为我们可以通过“更好的模型”来处理选择器和操作。


0
我不会在Action Creator中访问状态。我会使用mapStateToProps(),导入整个state对象和导入一个combinedReducer文件(或import * from './reducers';),在最终要使用Action Creator的组件中使用解构来使用所需的state prop。如果Action Creator将状态传递给给定类型的Reducer,您不需要提及状态,因为reducer可以访问当前设置的所有内容。您的示例未更新任何内容。我只会使用Action Creator从其参数传递状态。
在reducer中做如下操作:
const state = this.state;
const apple = this.state.apples;

如果您需要对您所引用的类型的状态执行操作,请在reducer中执行。

如果我错了,请纠正我!!!


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接