什么时候在 react/redux 中使用 bindActionCreators?

Posted

技术标签:

【中文标题】什么时候在 react/redux 中使用 bindActionCreators?【英文标题】:When would bindActionCreators be used in react/redux? 【发布时间】:2017-06-04 21:38:22 【问题描述】:

Redux 文档 bindActionCreators 声明:

bindActionCreators 的唯一用例是当您想将一些操作创建者传递给不了解 Redux 的组件,并且您不想将调度或 Redux 存储传递给它时。

使用/需要bindActionCreators 的示例是什么?

哪种组件不知道Redux

这两种选择的优缺点是什么?

//actionCreator
import * as actionCreators from './actionCreators'

function mapStateToProps(state) 
  return 
    posts: state.posts,
    comments: state.comments
  


function mapDispatchToProps(dispatch) 
  return bindActionCreators(actionCreators, dispatch)

vs

function mapStateToProps(state) 
  return 
    posts: state.posts,
    comments: state.comments
  


function mapDispatchToProps(dispatch) 
  return 
    someCallback: (postId, index) => 
      dispatch(
        type: 'REMOVE_COMMENT',
        postId,
        index
      )
    
  

【问题讨论】:

【参考方案1】:

我用它来创建 useActions 钩子:

import  useDispatch  from "react-redux";
import  bindActionCreators  from "redux";
import  actionCreators  from "../state";

export const useActions = () => 
  const dispatch = useDispatch();
  return bindActionCreators(actionCreators, dispatch);
;

actionCreators 是我从文件中导出的所有动作创建器函数。例如,假设我有 updatePost 动作创建者

export const updatePost = (id: string, content: string): UpdatePostAction => 
  return  type: ActionType.UPDATE_POST, payload:  id, content  ;
;

因此,每当我需要调度 updatePost 操作时,我都会这样写:

const updatePost=useActions()
updatePost(id,content)

【讨论】:

【参考方案2】:

我不认为最受欢迎的答案实际上解决了这个问题。

以下所有示例基本上都做同样的事情,并遵循无“预绑定”概念。

// option 1
const mapDispatchToProps = (dispatch) => (
  action: () => dispatch(action())
)


// option 2
const mapDispatchToProps = (dispatch) => (
  action: bindActionCreators(action, dispatch)
)


// option 3
const mapDispatchToProps = 
  action: action

选项 #3 只是选项 #1 的简写,所以真正的问题是为什么要使用选项 #1 与选项 #2。我已经看到它们都在 react-redux 代码库中使用,我发现它相当混乱。

我认为混淆来自以下事实:react-redux 文档中的所有examples 都使用bindActionCreators,而bindActionCreators 的文档(如问题本身所引用)表示不要将其与 react- 一起使用还原。

我想答案是代码库的一致性,但我个人更喜欢在需要时将操作明确地包装在 dispatch 中。

【讨论】:

选项#3 是选项#1 的简写吗? @ogostos github.com/reduxjs/react-redux/blob/master/docs/… @ArtemBernatskyi 谢谢。所以,mapDispatchToProps 有 3 种情况:functionobject 和缺失。每种情况如何处理定义here 我一直在寻找这个答案。谢谢。 我实际上遇到了 React 文档现在讨论的特定用例“将一些动作创建者传递给不了解 Redux 的组件”,因为我有一个简单的组件连接到一个更复杂的组件,如果我可以只传递一个道具,那么将reduxconnectaddDispatchToProps 的开销添加到简单组件似乎有点过分了。但据我所知,mapDispatch 到道具的几乎所有情况都是答案中提到的#1#3 选项【参考方案3】:

通过使用bindActionCreators,它可以对多个动作函数进行分组,并将其传递给像这样不了解 Redux(哑组件)的组件

// actions.js

export const increment = () => (
    type: 'INCREMENT'
)

export const decrement = () => (
    type: 'DECREMENT'
)
// main.js
import  Component  from 'react'
import  bindActionCreators  from 'redux'
import * as Actions from './actions.js'
import Counter from './counter.js'

class Main extends Component 

  constructor(props) 
    super(props);
    const  dispatch  = props;
    this.boundActionCreators = bindActionCreators(Actions, dispatch)
  

  render() 
    return (
      <Counter ...this.boundActionCreators />
    )
  

// counter.js
import  Component  from 'react'

export default Counter extends Component 
  render() 
    <div>
     <button onclick=() => this.props.increment()
     <button onclick=() => this.props.decrement()
    </div>
  

【讨论】:

在 react-redux 中使用 useDispatch() 时看起来很相似【参考方案4】:

bindActionCreators() 的一种可能用法是将多个动作“映射”在一起作为单个道具。

一个正常的调度看起来像这样:

将几个常见的用户操作映射到道具。

const mapStateToProps = (state: IAppState) => 
  return 
    // map state here
  

const mapDispatchToProps = (dispatch: Dispatch) => 
  return 
    userLogin: () => 
      dispatch(login());
    ,
    userEditEmail: () => 
      dispatch(editEmail());
    ,
  ;
;
export default connect(mapStateToProps, mapDispatchToProps)(MyComponent);

在较大的项目中,单独映射每个调度可能会让人感到笨拙。如果我们有一堆彼此相关的动作,我们可以组合这些动作。例如,执行各种不同用户相关操作的用户操作文件。我们可以使用bindActionCreators() 而不是dispatch,而不是将每个动作作为单独的调度调用。

使用 bindActionCreators() 进行多次分派

导入所有相关操作。它们很可能都在 redux 商店的同一个文件中

import * as allUserActions from "./store/actions/user";

现在不使用 dispatch 而是使用 bindActionCreators()

    const mapDispatchToProps = (dispatch: Dispatch) => 
      return 
           ...bindActionCreators(allUserActions, dispatch);
        ,
      ;
    ;
    export default connect(mapStateToProps, mapDispatchToProps, 
    (stateProps, dispatchProps, ownProps) => 
      return 
        ...stateProps,
        userAction: dispatchProps
        ownProps,
      
    )(MyComponent);

现在我可以使用 prop userAction 来调用组件中的所有操作。

IE: userAction.login() userAction.editEmail() 要么 this.props.userAction.login()this.props.userAction.editEmail().

注意:您不必将 bindActionCreators() 映射到单个道具。 (映射到userAction 的附加=&gt; return )。您还可以使用bindActionCreators() 将单个文件的所有操作映射为单独的道具。但我发现这样做可能会令人困惑。我更喜欢为每个动作或“动作组”指定一个明确的名称。我还喜欢将ownProps 命名为更能描述这些“儿童道具”是什么或它们来自哪里。使用 Redux + React 时,提供所有道具的位置可能会有点混乱,因此描述性越强越好。

【讨论】:

【参考方案5】:

我还想了解更多关于 bindActionsCreators 的信息,以下是我在项目中的实施方式。

// Actions.js
// Action Creator
const loginRequest = (username, password) => 
 return 
   type: 'LOGIN_REQUEST',
   username,
   password,
  


const logoutRequest = () => 
 return 
   type: 'LOGOUT_REQUEST'
  


export default  loginRequest, logoutRequest ;

在你的 React 组件中

import React,  Component  from 'react';
import  connect  from 'react-redux';
import  bindActionCreators  from 'redux';
import ActionCreators from './actions'

class App extends Component 
  componentDidMount() 
   // now you can access your action creators from props.
    this.props.loginRequest('username', 'password');
  

  render() 
    return null;
  


const mapStateToProps = () => null;

const mapDispatchToProps = dispatch => ( ...bindActionCreators(ActionCreators, dispatch) );

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

【讨论】:

【参考方案6】:

bindActionCreators 的一个很好的用例是使用redux-saga-routines 与redux-saga 集成。例如:

// routines.js
import  createRoutine  from "redux-saga-routines";
export const fetchPosts = createRoutine("FETCH_POSTS");
// Posts.js
import React from "react";
import  bindActionCreators  from "redux";
import  connect  from "react-redux";
import  fetchPosts  from "routines";

class Posts extends React.Component 
  componentDidMount() 
    const  fetchPosts  = this.props;
    fetchPosts();
  

  render() 
    const  posts  = this.props;
    return (
      <ul>
        posts.map((post, i) => (
          <li key=i>post</li>
        ))
      </ul>
    );
  


const mapStateToProps = ( posts ) => ( posts );
const mapDispatchToProps = dispatch => (
  ...bindActionCreators( fetchPosts , dispatch)
);

export default connect(
  mapStateToProps,
  mapDispatchToProps
)(Posts);
// reducers.js
import  fetchPosts  from "routines";

const initialState = [];

export const posts = (state = initialState,  type, payload ) => 
  switch (type) 
    case fetchPosts.SUCCESS:
      return payload.data;
    default:
      return state;
  
;
// api.js
import axios from "axios";

export const JSON_OPTS =  headers:  Accept: "application/json"  ;
export const GET = (url, opts) =>
  axios.get(url, opts).then(( data, headers ) => ( data, headers ));
// sagas.js
import  GET, JSON_OPTS  from "api";
import  fetchPosts  from "routines";
import  call, put, takeLatest  from "redux-saga/effects";

export function* fetchPostsSaga() 
  try 
    yield put(fetchPosts.request());
    const  data  = yield call(GET, "/api/posts", JSON_OPTS);
    yield put(fetchPosts.success(data));
   catch (error) 
    if (error.response) 
      const  status, data  = error.response;
      yield put(fetchPosts.failure( status, data ));
     else 
      yield put(fetchPosts.failure(error.message));
    
   finally 
    yield put(fetchPosts.fulfill());
  


export function* fetchPostsRequestSaga() 
  yield takeLatest(fetchPosts.TRIGGER, fetchPostsSaga);

请注意,此模式可以使用 React Hooks 实现(从 React 16.8 开始)。

【讨论】:

【参考方案7】:

docs声明很明确:

bindActionCreators 的唯一用例是当您想要将一些操作创建者传递给不了解 Redux 的组件,并且您不想将调度或 Redux 存储传递给它时。

这显然是一个可能出现在以下且只有一个条件的用例:

假设我们有组件 A 和 B:

// A use connect and updates the redux store
const A = props => 
export default connect()(A)

// B doesn't use connect therefore it does not know about the redux store.
const B = props => 
export default B

注入 react-redux: (A)

const boundActionCreators = bindActionCreators(SomeActionCreator, dispatch)
// myActionCreatorMethod,
// myActionCreatorMethod2,
// myActionCreatorMethod3,

// when we want to dispatch
const action = SomeActionCreator.myActionCreatorMethod('My updates')
dispatch(action)

由 react-redux 注入:(B)

const  myActionCreatorMethod  = props
<B myActionCreatorMethod=myActionCreatorMethod ...boundActionCreators />

注意到以下情况了吗?

我们通过组件 A 更新了 redux 存储,而我们不知道组件 B 中的 redux 存储。

我们不会在组件 A 中更新。要了解我的确切意思,您可以探索 this post。我希望你有一个想法。

【讨论】:

我什么都不懂【参考方案8】:

我会尝试回答最初的问题...

智能和哑组件

在您的第一个问题中,您基本上会问为什么首先需要 bindActionCreators,以及哪些组件不应该了解 Redux。

简而言之,这里的想法是将组件分为 smart(容器)和 dumb(展示)组件。 哑组件在需要知道的基础上工作。他们的核心工作是将给定的数据呈现为 html,仅此而已。他们不应该知道应用程序的内部运作。它们可以看作是应用程序的最底层。

另一方面,智能组件是一种胶水,它为dumb组件准备数据,最好不渲染HTML。

这种架构促进了 UI 层和下面的数据层之间的松散耦合。这反过来又允许用其他东西(即 UI 的新设计)轻松替换两层中的任何一层,而不会破坏另一层。

回答你的问题:愚蠢的组件不应该知道 Redux(或任何不必要的数据层实现细节),因为我们将来可能想用其他东西替换它。

您可以在 Redux manual 中找到有关此概念的更多信息,在 Dan Abramov 的文章 Presentational and Container Components 中更深入地了解。

哪个例子更好

第二个问题是关于给定示例的优点/缺点。

第一个示例中,动作创建者在单独的actionCreators 文件/模块中定义,这意味着它们可以在其他地方重复使用。这几乎是定义动作的标准方式。我真的看不出这有什么缺点。

第二个示例内联定义了动作创建者,它有多个缺点:

动作创建者不能重复使用(显然) 事情比较冗长,这意味着可读性较差 动作类型是硬编码的——最好将它们分别定义为consts,以便它们可以在reducer中引用——这样可以减少输入错误的机会 内联定义动作创建者不符合推荐/预期的使用方式 - 如果您打算共享代码,这将使您的代码对社区的可读性降低

第二个例子比第一个例子有一个优势——写起来更快!因此,如果您对自己的代码没有更大的计划,那可能还好。

我希望我设法澄清了一点......

【讨论】:

【参考方案9】:

更完整的例子,传递一个充满动作创建者的对象来连接:

import * as ProductActions from './ProductActions';

// component part
export function Product( name, description ) 
    return <div>
        <button onClick=this.props.addProduct>Add a product</button>
    </div>


// container part
function mapStateToProps(state) 
    return ...state;


function mapDispatchToProps(dispatch) 
    return bindActionCreators(
        ...ProductActions,
    , dispatch);


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

【讨论】:

这应该是答案【参考方案10】:

99% 的情况下,它与 React-Redux connect() 函数一起使用,作为 mapDispatchToProps 参数的一部分。它可以在您提供的mapDispatch 函数中显式使用,或者如果您使用对象简写语法并将一个充满动作创建者的对象传递给connect,则可以自动使用它。

这个想法是,通过预先绑定动作创建者,您传递给connect() 的组件在技术上“不知道”它已连接——它只知道它需要运行this.props.someCallback()。另一方面,如果您没有绑定动作创建者并调用this.props.dispatch(someActionCreator()),那么现在组件“知道”它已连接,因为它期望props.dispatch 存在。

我在我的博文Idiomatic Redux: Why use action creators? 中写了一些关于这个主题的想法。

【讨论】:

但它与'mapDispatchToProps'连接,这很好。似乎绑定动作创建者实际上是一件消极/毫无意义的事情,因为在调试等许多其他事情中,您将丢失函数定义(TS 或 Flow)。在我较新的项目中,我从不使用它,迄今为止我还没有遇到过问题。还使用 Saga 和持久状态。此外,如果你只是调用 redux 函数(虽然你得到了不错的点击),我会说这更干净,然后“附加”动作创建者在我看来是混乱和毫无意义的。您的智能(通常是屏幕组件)仍然可以使用连接,但仅用于道具。

以上是关于什么时候在 react/redux 中使用 bindActionCreators?的主要内容,如果未能解决你的问题,请参考以下文章

初学react + redux

React+router和react+redux使用过程的记录

在react/redux中使用Immutable

react/redux 应用程序:未调度到存储的操作

react-redux原理

React Redux表单提交未给出正确的结果