前面用六个专栏说了 Redux 的核心基础概念。如果你搞懂了这些东西,在实际的开发中使用 Redux 进行状态管理是没有问题的。但是,为了更好的让 Redux 为我们的项目服务、更优雅的管理我们项目中的状态,我们应该不仅限于此,而是知其全貌,晓其原理。
Action 与 ActionCreator
action 是一个动作 —— 一个描述修改 State 状态数据的动作。在前面的内容中,我们一再强调:Redux 中修改数据状态唯一的途径就是通过 dispatch 发送一个 action 到 reducer。在 reducer 逻辑中,会根据 action 的描述做相应的 State 数据更改操作。
action 描述是修改 state 数据的动作。而在我们实际的项目中,需要描述的动作肯定很多,这样重复的代码自然而然的就多了起来。为了项目更好的管理,代码极大的得到复用,我们在 Redux 系列三:Action 和 Action Creator 说到了 ActionCreator。
根据 action 的通用构造,我们可以通过 ActionCreator 的形式构造一些返回 action 对象的函数,形如:
const ADD_DATA = 'ADD_DATA'
function addData(data) {
return {
type: ADD_TODO,
payload: data
}
}
当然,你可以封装的更好,比如按功能模块封装:
function userModuleAction(type, data) {
return {
type,
payload: data
}
}
当然,封装的形式,你还是需要根据自己项目和团队的情况进行处理。
bindActionCreators
使用场景
当然,上面说到的 action 和 ActionCreator 只是 Redux 提出的概念和封装方案,并没有通过实际的 API 去处理。相反,Redux 暴露除了一个方法 —— bindActionCreators。它的使用场景比较单一,在实际的项目开发中可以完全没比较使用它。
惟一会使用的场景是:需要把 ActionCreator 向下传递到另一个组件上,但又不想让这个组件觉察到 Redux 的存在,而且也不希望把 dispatch 或 Redux store 传给它。
这句话什么意思呢?我们从一个较大的业务组件中抽取出了若干个单一功能的业务组件,我们希望这些小型的业务组件可以得到复用,所以并不希望在他们的逻辑中引入 action、store、dispatch 什么的,就只想在适当的生命周期方法或者事件中执行 dispatch 获取异步数据,更新 state 数据,渲染页面。当然,为了更好的复用,组件的业务逻辑也不应该含有特性业务逻辑的代码,比如特性的 action 等。所以 bindActionCreators 的用途就来了。
实例
首先,我们建立了这样 actions.js
let nextTodoId = 0
export const addTodo = text => ({
type: 'ADD_TODO',
id: nextTodoId++,
text
})
export const removeTodo = id => ({
type: 'REMOVE_TODO',
id,
})
实际用于的容易组件 container.js
import { Component } from 'react';
import { bindActionCreators } from 'redux';
import store from '../store'
import * as TodoActionCreators from './TodoActionCreators';
console.log(TodoActionCreators);
// {
// addTodo: Function,
// removeTodo: Function
// }
class TodoListContainer extends Component {
constructor(props) {
super(props);
// 这是一个很好的 bindActionCreators 的使用示例:
// 你想让你的子组件完全不感知 Redux 的存在。
// 我们在这里对 action creator 绑定 dispatch 方法,
// 以便稍后将其传给子组件。
this.boundActionCreators = bindActionCreators(TodoActionCreators, store.dispatch);
console.log(this.boundActionCreators);
// {
// addTodo: Function,
// removeTodo: Function
// }
}
componentDidMount() {
let action = TodoActionCreators.addTodo('Use Redux');
store.dispatch(action);
}
render() {
return <TodoList {...this.boundActionCreators} />;
}
}
在上面的示例代码中,我们会通过 import * as 的形式将 actionCreators 全部取出来,然后和 store.dispatch 以参数的形式传入 bindActionCreators。经过函数调用后,会得到函数或者以函数为 value 的对象,然后我们就会将它传入子组件。在子组件中,如果需要发起一个修改 state 状态数据的 dispatch 直接执行函数或者对应的键值函数就可以了,整个子组件的逻辑没有任何关于 Redux 的逻辑,可以很好的进行复用。
源码分析
bindActionCreators 函数接受的 actionCreators 和 dispatch,返回的是函数或以函数为 value 的对象,那 bindActionCreators 函数实际做了什么呢?代码如下:
function bindActionCreator(actionCreator, dispatch) {
return function() {
return dispatch(actionCreator.apply(this, arguments))
}
}
export default function bindActionCreators(actionCreators, dispatch) {
if (typeof actionCreators === 'function') {
return bindActionCreator(actionCreators, dispatch)
}
if (typeof actionCreators !== 'object' || actionCreators === null) {
throw new Error(
`bindActionCreators expected an object or a function, instead received ${
actionCreators === null ? 'null' : typeof actionCreators
}. ` +
`Did you write "import ActionCreators from" instead of "import * as ActionCreators from"?`
)
}
const boundActionCreators = {}
for (const key in actionCreators) {
const actionCreator = actionCreators[key]
if (typeof actionCreator === 'function') {
boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
}
}
return boundActionCreators
}
源代码很简单了,先会判断 actionCreators 的类型。如果是函数,就直接执行 bindActionCreator 函数,获取一个包装函数;如果是对象,就遍历对象的每个 key,获取对应的 value,判断 value 是否为函数,如果是函数就执行 bindActionCreator 函数,将返回的包装函数设置到 boundActionCreators 对应的 key 上,最终返回的就是以包装了 dispatch 函数为 value 的对象;如果 actionCreators 的类型既不是函数,也不是对象,就报错。
总结
bindActionCreators 函数是 Redux 提供的在特殊业务场景下更好使用 actionCreator 的方式。它将所有的 actionCreator 进行处理后,会利用闭包的特性生成包装了 dispatch 函数。然后将结果传递给子组件。在子组件中,如果需要通过调用 dispatch 修改 state 状态数据,就直接调用对应的函数就可以了,而不用包含任何的 Redux 逻辑代码,使组件达到了低耦合,组件复用性就更强了。