react/redux 什么时候使用 bindActionCreators?

When would bindActionCreators be used in react/redux?

Redux bindActionCreators 的文档指出:

The only use case for bindActionCreators is when you want to pass some action creators down to a component that isn't aware of Redux, and you don't want to pass dispatch or the Redux store to it.

bindActionCreators 变成 used/needed 的例子是什么?

哪种组件不会意识到 Redux

两个选项的advantages/disadvantages是什么?

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

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

则自动使用

这个想法是,通过 pre-binding 动作创建者,您传递给 connect() 的组件在技术上 "doesn't know" 它已连接 - 它只知道它需要 运行 this.props.someCallback()。另一方面,如果您没有绑定 action creators,并调用了 this.props.dispatch(someActionCreator()),现在它连接的组件 "knows" 因为它期望 props.dispatch 存在。

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

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

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

我会尽量回答原来的问题...

聪明和笨拙的组件

在你的第一个问题中,你基本上是在问为什么首先需要 bindActionCreators,什么样的组件不应该知道 Redux。

简而言之,这里的想法是组件应该分成 smart(容器)和 dumb(展示)组件。 哑组件 在需要知道的基础上工作。他们的灵魂工作是将给定的数据呈现给 HTML,仅此而已。他们不应该知道应用程序的内部运作。它们可以被视为您应用程序的表皮深层。

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

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

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

您可以在 Dan Abramov 的 Redux manual and in greater depth in article Presentational and Container Components 中找到有关此概念的更多信息。

哪个例子更好

第二个问题是关于给定示例中的 advantages/disadvantages 个。

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

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

  • 不能重复使用动作创建者(显然)
  • 内容比较冗长,可读性较差
  • action 类型是硬编码的 - 最好将它们单独定义为 consts,这样它们就可以在 reducer 中被引用 - 这将减少输入错误的机会
  • 内联定义动作创建者违反 recommended/expected 使用它们的方式 - 如果您打算共享您的代码,这将降低您的代码对社区的可读性

与第一个示例相比,第二个示例具有 一个优势 - 编写速度更快!因此,如果您对代码没有更大的计划,它可能会很好。

我希望我能把事情弄清楚一点...

我认为最受欢迎的答案实际上并没有解决问题。

下面的所有示例本质上都做同样的事情并遵循无 "pre-binding" 概念。

// 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 的 shorthand ,所以真正的问题是为什么要使用选项 #1 与选项 #2。我已经看到它们都用于 react-redux 代码库,我发现它相当混乱。

我认为混淆来自这样一个事实,即所有 examples in react-redux doc uses bindActionCreators while the doc for bindActionCreators(如问题本身所引用)都说不要将它与 react-redux 一起使用。

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

docs的说法很明确:

The only use case for bindActionCreators is when you want to pass some action creators down to a component that isn't aware of Redux, and you don't want to pass dispatch or the Redux store to it.

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

比方说,我们有组件 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 store,而我们不知道组件 B 中的 redux store。

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

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

// 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 开始)实现。

我也在寻找更多关于 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);

bindActionCreators() 的一个可能用途是 "map" 多个动作一起作为一个道具。

一个正常的调度是这样的:

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

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

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

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

注意:您不必将 bindActionCreators() 映射到单个道具。 (映射到 userAction 的附加 => {return {}})。您还可以使用 bindActionCreators() 将单个文件的所有操作映射为单独的道具。但我发现这样做可能会造成混淆。我更喜欢为每个操作或 "action group" 指定一个明确的名称。我还喜欢将 ownProps 命名为更能描述这些 "child props" 是什么或它们来自哪里。使用 Redux + React 时,所有 props 的提供位置可能会有点混乱,因此描述性越强越好。

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

我用它来创建 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 action creator

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

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

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