在动作创建者中访问Redux状态?


296

说我有以下几点:

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

在该动作创建者中,我想访问全局商店状态(所有reducer)。这样做更好吗?

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

Answers:


521

关于访问行动创建者的状态是否是一个好主意,存在不同的看法:

  • Redux的创建者Dan Abramov认为应该加以限制:“我认为可接受的几种用例是在发出请求之前检查缓存的数据,或者检查您是否已通过身份验证(换句话说,进行有条件的分发)。我认为,通过数据state.something.items在行动的创建者绝对是一个反模式和气馁,因为它掩盖了改变历史:如果有一个错误,items是不正确的,这是很难追查,其中那些不正确的值来自于,因为它们是已经是动作的一部分,而不是由减速器直接对动作进行计算。因此,请谨慎操作。”
  • 目前Redux的维护者Mark Erikson说,这很好,甚至鼓励getState在thunk中使用-这就是它存在的原因。他在博客文章“ 惯用的Redux:关于Thunk,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的更多信息,以及在这种情况下为什么需要这样做的更多信息。

理想情况下,您的操作不应是“胖”的,并且应包含尽可能少的信息,但是您应该随意在自己的应用程序中进行最适合自己的事情。的终极版FAQ对信息行动的创作者和减速器之间拆分逻辑时间时,它可以是使用有用的getState一个动作的创造者


5
在一种情况下,选择组件中的某些内容可能会触发PUT或POST,这取决于存储区是否包含与组件相关的数据。最好将用于PUT / POST选择的业务逻辑放在组件中,而不是基于基于thunk的动作创建器中?
vicusbass's

3
最佳做法是什么?我现在在动作创建者中使用getState时遇到了类似的问题。就我而言,我用它来确定值是否有一个待处理的更改(如果是的话,我将调度一个显示对话框的操作)。
Arne H. Bitubekk

42
可以从动作创建者的商店中读取内容。我鼓励您使用选择器,以免依赖于确切的状态形状。
Dan Abramov

2
我正在使用中间件将数据发送到mixpanel。所以我在操作中有一个meta键。我需要将不同的变量从状态传递到混合面板。将它们放在动作创建者身上似乎是一种反模式。处理此类用例的最佳方法是什么?
Aakash Sigdel

2
天啊!我没有打结redux thunk会收到getState第二个参数,我一直
挣扎

33

当您的情况很简单时,您可以使用

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才能在动作创建者中返回函数


3
我可以问一下是否应该检查“加载”状态,以便在第一个ajax请求完成时不发出另一个ajax请求吗?
JoeTidee

@JoeTidee在此示例中,完成了加载状态的分配。例如,如果使用按钮执行此操作,则将检查是否loading === true存在该按钮并禁用该按钮。
croraf

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

这种方法很合逻辑。
AhmetŞimşek18年

这是正确的方法以及我的操作方法。动作创建者不需要知道整个状态,而只需要知道与之相关的那一点即可。

3

我想指出的是,从存储中读取并没有那么糟糕-基于存储确定要做什么应该比将所有内容都传递给组件然后作为参数来方便得多。一个功能。我完全同意Dan的观点,最好不要将store作为单调使用,除非您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的解决方案更好或更糟。

通过将动作分为2个单独的功能,您可以将状态从化简器转换为动作:首先索要数据,其次对数据进行动作。您可以使用来做到这一点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,
    }
}

希望这对某人有帮助。


0

我知道我在这里参加晚会很晚,但是我来到这里是出于自己对在行动中运用国家的愿望的看法,然后当我意识到自己的正确行为时就形成了自己的观点。

这是选择器对我最有意义的地方。应该告知您发出此请求的组件,是否该通过选择来发出它了。

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

感觉就像泄漏了抽象,但是您的组件显然需要发送一条消息,并且消息有效负载应包含相关状态。不幸的是,您的问题没有具体的示例,因为我们可以通过这种更好的选择器和操作模型来工作。


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

如果要重用,则必须提取特定的mapStatemapDispatch并将其提取mergeProps到要在其他位置重用的函数中,但这会使依赖关系非常清晰。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.