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

Posted

技术标签:

【中文标题】在动作创建者中访问 Redux 状态?【英文标题】:Accessing Redux state in an action creator? 【发布时间】:2016-06-10 14:02:58 【问题描述】:

假设我有以下内容:

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】:

当你的场景很简单时,你可以使用

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

【讨论】:

我能问一下是否应该检查状态“加载”的状态,以便在第一个 ajax 请求完成时不会发出另一个 ajax 请求? @JoeTidee 在示例中,加载状态的调度已完成。例如,如果您使用按钮执行此操作,您将检查是否有loading === true 并禁用该按钮。【参考方案2】:

对于在动作创建者中访问状态是否是一个好主意存在不同意见:

Redux 创建者 Dan Abramov 认为它应该受到限制:“我认为可以接受的少数用例是在您发出请求之前检查缓存数据,或者检查您是否经过身份验证(换句话说,执行条件dispatch). 我认为在动作创建者中传递诸如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 是从某个模块导出的单例。 我们不建议这样做,因为它使add server rendering to your app 变得更加困难,因为在大多数情况下在服务器上,您希望每个请求都有一个单独的存储。因此,虽然从技术上讲这种方法可行,但我们不建议从模块中导出商店。

这就是我们推荐第二种方法的原因:

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 常见问题解答包含有关 splitting logic between action creators and reducers 和 times when it may be useful to use getState in an action creator 的信息。

【讨论】:

我有一种情况,在组件中选择某些内容可能会触发 PUT 或 POST,这取决于存储是否包含与组件相关的数据。将 PUT/POST 选择的业务逻辑放在组件中而不是基于 thunk 的动作创建器中是否更好? 最佳实践是什么?我现在面临着在我的动作创建器中使用 getState 的类似问题。在我的情况下,我使用它来确定表单是否有待处理的更改(如果是,我将调度一个显示对话框的操作)。 从动作创建器中的存储读取就可以了。我鼓励您use a selector,这样您就不必依赖于确切的状态形状。 我正在使用中间件将数据发送到 mixpanel。所以我在动作中有一个元键。我需要将不同的变量从状态传递到混合面板。将它们设置在动作创建者身上似乎是一种反模式。处理这类用例的最佳方法是什么? 天哪!我没有把那个 redux thunk 接收到getState 作为第二个参数,我脑袋坏了,非常感谢【参考方案3】:

我想指出,从 store 中读取并没有那么糟糕——根据 store 决定应该做什么可能比将所有内容传递给组件然后作为一个函数的参数。我完全同意 Dan 的观点,最好不要将 store 用作单调,除非您 100% 确定您将仅用于客户端渲染(否则可能会出现难以追踪的错误)。

I have created a library 最近在处理 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())
    ;
  ,
);

但是,如您所见,我们并没有真正在此处修改数据,因此我们很有可能可以在其他地方使用此选择器将其组合到其他地方。

【讨论】:

【参考方案4】:

我同意@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);

【讨论】:

这个方法比较合乎逻辑。 这是正确的做法以及我的做法。你的动作创建者不需要知道整个状态,只需要知道与之相关的部分。【参考方案5】:

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

您可以通过将操作拆分为 2 个单独的函数来将减速器的状态转换为操作:首先请求数据,然后对数据执行操作。您可以使用redux-loop 来做到这一点。

首先“请提供数据”

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

在reducer中,通过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));
  

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

【讨论】:

【参考方案7】:

我想提出另一种我认为最干净的替代方案,但它需要 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 提取到函数中以在其他地方重用,但这使得依赖关系非常清晰。

【讨论】:

【参考方案8】:

我不会访问 Action Creator 中的状态。我将使用 mapStateToProps() 并导入整个状态对象,并在 Action Creator 最终将要进入的组件中导入 combineReducer 文件(或 import * from './reducers';)。然后在组件中使用解构来使用 state prop 中的任何内容。如果 Action Creator 将给定 TYPE 的状态传递给 Reducer,则无需提及 state,因为 reducer 可以访问当前设置为 state 的所有内容。您的示例没有更新任何内容。我只会使用 Action Creator 从其参数中传递状态。

在 reducer 中执行如下操作:

const state = this.state;
const apple = this.state.apples;

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

如有错误请指正!!!

【讨论】:

以上是关于在动作创建者中访问 Redux 状态?的主要内容,如果未能解决你的问题,请参考以下文章

Redux:动作已成功调度,但未创建状态

Redux:将参数传递给可以从状态中检索的动作?

React Native Redux:动作分派,返回结果

Redux 状态不会随着动作调度而更新

所有的 redux 动作都应该有相应的 reducer 吗?

在 react redux 中全局状态更改后调度新动作