前面用六个专栏说了 Redux 的核心基础概念。如果你搞懂了这些东西,在实际的开发中使用 Redux 进行状态管理是没有问题的。但是,为了更好的让 Redux 为我们的项目服务、更优雅的管理我们项目中的状态,我们应该不仅限于此,而是知其全貌,晓其原理。

Action 与 ActionCreator

action 是一个动作 —— 一个描述修改 State 状态数据的动作。在前面的内容中,我们一再强调:Redux 中修改数据状态唯一的途径就是通过 dispatch 发送一个 action 到 reducer。在 reducer 逻辑中,会根据 action 的描述做相应的 State 数据更改操作。

action 描述是修改 state 数据的动作。而在我们实际的项目中,需要描述的动作肯定很多,这样重复的代码自然而然的就多了起来。为了项目更好的管理,代码极大的得到复用,我们在 Redux 系列三:Action 和 Action Creator 说到了 ActionCreator。

根据 action 的通用构造,我们可以通过 ActionCreator 的形式构造一些返回 action 对象的函数,形如:

  1. const ADD_DATA = 'ADD_DATA'
  2. function addData(data) {
  3. return {
  4. type: ADD_TODO,
  5. payload: data
  6. }
  7. }

当然,你可以封装的更好,比如按功能模块封装:

  1. function userModuleAction(type, data) {
  2. return {
  3. type,
  4. payload: data
  5. }
  6. }

当然,封装的形式,你还是需要根据自己项目和团队的情况进行处理。

bindActionCreators

使用场景

当然,上面说到的 action 和 ActionCreator 只是 Redux 提出的概念和封装方案,并没有通过实际的 API 去处理。相反,Redux 暴露除了一个方法 —— bindActionCreators。它的使用场景比较单一,在实际的项目开发中可以完全没比较使用它。

惟一会使用的场景是:需要把 ActionCreator 向下传递到另一个组件上,但又不想让这个组件觉察到 Redux 的存在,而且也不希望把 dispatch 或 Redux store 传给它。

这句话什么意思呢?我们从一个较大的业务组件中抽取出了若干个单一功能的业务组件,我们希望这些小型的业务组件可以得到复用,所以并不希望在他们的逻辑中引入 action、store、dispatch 什么的,就只想在适当的生命周期方法或者事件中执行 dispatch 获取异步数据,更新 state 数据,渲染页面。当然,为了更好的复用,组件的业务逻辑也不应该含有特性业务逻辑的代码,比如特性的 action 等。所以 bindActionCreators 的用途就来了。

实例

首先,我们建立了这样 actions.js

  1. let nextTodoId = 0
  2. export const addTodo = text => ({
  3. type: 'ADD_TODO',
  4. id: nextTodoId++,
  5. text
  6. })
  7. export const removeTodo = id => ({
  8. type: 'REMOVE_TODO',
  9. id,
  10. })

实际用于的容易组件 container.js

  1. import { Component } from 'react';
  2. import { bindActionCreators } from 'redux';
  3. import store from '../store'
  4. import * as TodoActionCreators from './TodoActionCreators';
  5. console.log(TodoActionCreators);
  6. // {
  7. // addTodo: Function,
  8. // removeTodo: Function
  9. // }
  10. class TodoListContainer extends Component {
  11. constructor(props) {
  12. super(props);
  13. // 这是一个很好的 bindActionCreators 的使用示例:
  14. // 你想让你的子组件完全不感知 Redux 的存在。
  15. // 我们在这里对 action creator 绑定 dispatch 方法,
  16. // 以便稍后将其传给子组件。
  17. this.boundActionCreators = bindActionCreators(TodoActionCreators, store.dispatch);
  18. console.log(this.boundActionCreators);
  19. // {
  20. // addTodo: Function,
  21. // removeTodo: Function
  22. // }
  23. }
  24. componentDidMount() {
  25. let action = TodoActionCreators.addTodo('Use Redux');
  26. store.dispatch(action);
  27. }
  28. render() {
  29. return <TodoList {...this.boundActionCreators} />;
  30. }
  31. }

在上面的示例代码中,我们会通过 import * as 的形式将 actionCreators 全部取出来,然后和 store.dispatch 以参数的形式传入 bindActionCreators。经过函数调用后,会得到函数或者以函数为 value 的对象,然后我们就会将它传入子组件。在子组件中,如果需要发起一个修改 state 状态数据的 dispatch 直接执行函数或者对应的键值函数就可以了,整个子组件的逻辑没有任何关于 Redux 的逻辑,可以很好的进行复用。

源码分析

bindActionCreators 函数接受的 actionCreators 和 dispatch,返回的是函数或以函数为 value 的对象,那 bindActionCreators 函数实际做了什么呢?代码如下:

  1. function bindActionCreator(actionCreator, dispatch) {
  2. return function() {
  3. return dispatch(actionCreator.apply(this, arguments))
  4. }
  5. }
  6. export default function bindActionCreators(actionCreators, dispatch) {
  7. if (typeof actionCreators === 'function') {
  8. return bindActionCreator(actionCreators, dispatch)
  9. }
  10. if (typeof actionCreators !== 'object' || actionCreators === null) {
  11. throw new Error(
  12. `bindActionCreators expected an object or a function, instead received ${
  13. actionCreators === null ? 'null' : typeof actionCreators
  14. }. ` +
  15. `Did you write "import ActionCreators from" instead of "import * as ActionCreators from"?`
  16. )
  17. }
  18. const boundActionCreators = {}
  19. for (const key in actionCreators) {
  20. const actionCreator = actionCreators[key]
  21. if (typeof actionCreator === 'function') {
  22. boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
  23. }
  24. }
  25. return boundActionCreators
  26. }

源代码很简单了,先会判断 actionCreators 的类型。如果是函数,就直接执行 bindActionCreator 函数,获取一个包装函数;如果是对象,就遍历对象的每个 key,获取对应的 value,判断 value 是否为函数,如果是函数就执行 bindActionCreator 函数,将返回的包装函数设置到 boundActionCreators 对应的 key 上,最终返回的就是以包装了 dispatch 函数为 value 的对象;如果 actionCreators 的类型既不是函数,也不是对象,就报错。

总结

bindActionCreators 函数是 Redux 提供的在特殊业务场景下更好使用 actionCreator 的方式。它将所有的 actionCreator 进行处理后,会利用闭包的特性生成包装了 dispatch 函数。然后将结果传递给子组件。在子组件中,如果需要通过调用 dispatch 修改 state 状态数据,就直接调用对应的函数就可以了,而不用包含任何的 Redux 逻辑代码,使组件达到了低耦合,组件复用性就更强了。