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


91

用于bindActionCreators的Redux文档指出:

唯一的用例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)
}

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

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

Answers:


58

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

下面的所有示例基本上都执行相同的操作,并且遵循“无预绑定”的概念。

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


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


// option 3
const mapDispatchToProps = {
  action: action
}

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

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

我想答案是代码库中的一致性,但是我个人更喜欢在需要时在分派中显式地包装动作。


2
选项是选项#3的缩写#1吗?
ogostos '18 -10-15


@ArtemBernatskyi谢谢。因此,事实证明存在3种情况mapDispatchToProps:和functionobject以及缺失。此处
ogostos,

我一直在寻找这个答案。谢谢。
Kishan Rajdev

我实际上遇到过React文档现在谈论的特定用例“将一些动作创建者传递给一个不了解Redux的组件”,因为我有一个简单的组件连接到一个更复杂的组件并增加了开销中reduxconnectaddDispatchToProps以简单的组件似乎矫枉过正,如果我可以只通过一个道具了。但据我所知,几乎所有支持mapDispatch道具的情况都将成为选项,#1或者#3在答案中提到
icc97

48

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

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

我在博客文章Idiomatic Redux中写了一些关于此主题的想法:为什么使用动作创建者?


1
但它与“ mapDispatchToProps”连接,很好。似乎绑定动作创建者实际上是消极/毫无意义的事情,因为在调试等许多其他事情中,您将失去功能定义(TS或Flow)。在我的较新项目中,我从未使用过它,迄今为止我还没有遇到任何问题。还使用佐贺并保持状态。另外,如果您只是调用redux函数(虽然单击效果不错),那我说它更加干净,然后“附加”动作创建者,在我看来这是混乱且毫无意义的。您的智能设备(通常是屏幕组件)仍然可以使用connect,但仅适用于道具。
奥利弗·迪克森

19

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

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

这应该是答案
Deen John

16

我将尝试回答原始问题...

智能和哑巴组件

在第一个问题中,您首先会问为什么bindActionCreators需要这样做,以及哪种组件不应该了解Redux。

简而言之,这里的想法是组件应分为智能(容器)和(呈现)组件。 哑巴组件在需要了解的基础上工作。他们的灵魂工作是将给定的数据呈现为HTML,仅此而已。他们不应该知道应用程序的内部工作原理。可以将它们视为应用程序的皮肤最深层。

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

这种体系结构促进了UI层与下面的数据层之间的松散耦合。从而可以轻松地用其他东西(即UI的新设计)替换两层中的任何一层,而不会破坏另一层。

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

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

哪个例子更好

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

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

第二个例子中定义了动作的创作者的内联,这具有多个缺点:

  • 动作创建者无法重复使用(显然)
  • 事情比较冗长,意味着可读性降低
  • 操作类型是硬编码的-最好将它们consts分别定义,以便可以在化简器中引用它们-这样可以减少键入错误的机会
  • 内联定义动作创建者违反了使用它们的推荐/预期方式-如果计划共享代码,这会使社区对代码的可读性降低

第二个示例比第一个示例具有一个优势 -编写起来更快!因此,如果您没有更好的代码计划,那可能很好。

我希望我能澄清一些事情...


2

的一种可能用法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";

现在,不使用调度,而是使用bindActionCreators()

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

现在,我可以使用道具userAction来调用组件中的所有动作。

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

注意:您不必将bindActionCreators()映射到单个道具。(=> {return {}}映射到的其他 userAction)。您还可以bindActionCreators()将单个文件的所有操作映射为单独的道具。但是我发现这样做可能会造成混淆。我更喜欢给每个动作或“动作组”一个明确的名称。我还想命名该名称,ownProps以更加描述这些“儿童道具”的含义或来源。当使用Redux + React时,会在提供所有道具的地方有些混乱,因此描述性越好。


2

通过使用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>
  }
}

1

我还想了解有关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);

0

一个很好的用例bindActionCreators是使用redux-saga-routinesredux-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开始)实现此模式。


-1

文档的语句是很清楚的:

唯一的用例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中进行更新。要确切地理解我的意思,您可以浏览这篇文章。希望你有个主意。


7
我什么都不懂
vsync
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.