超级面板
文章目录
最新文章
最近更新
文章分类
标签列表
文章归档

bindActionCreators.js 源码阅读笔记

Turns an object whose values are action creators, into an object with the same keys, but with every action creator wrapped into a dispatch call so they may be invoked directly.

接口

bindActionCreators(actionCreators, dispatch)

  • actionCreators (Function | Object): action creator 函数,或者键值是 action creators 的对象。
  • dispatch (Function): dispatch action 的函数。
  • return (Function | Object): 返回一个 dispatcher 函数或者对象,对象的每个属性值都是一个 dispatcher, 即可以dispatch action 的函数

理解

bindActionCreators 文档里是这么说的:把 action creators 转换成有相同 keys 的对象,并把每个 action creator 使用 dispatch 包围起来,这样可以直接调用它们。

这种说法听起来很绕口,先看个例子:

var addTodo = todo => ({
type: 'ADD_TODO',
todo
});

var removeTodo = todo => ({
type: 'REMOVE_TODO',
todo
});

var reducer = (state, action) => {
switch (action.type) {
case 'ADD_TODO':
return state.add(action.text);
case 'REMOVE_TODO':
return state.remove(action.text);
default:
return state;
}
};

var store = redux.createStore(reducer, []);

这是一个很常见的 redux 的例子,我们触发 ADD_TODO 通过 store.dispatch(addTodo("To read.")),bindActionCreators 实际上就是对这个过程做了处理:

// 单独为每个action创建:
var addTodoDispatcher = bindActionCreators(TodoActionCreators, dispatch);
var removeTodoDispatcher = bindActionCreators(TodoActionCreators, dispatch);

// 或者组合为所有action创建:
var todoDispatchers = bindActionCreators({
addTodo: addTodo,
removeTodo: removeTodo
}, dispatch);

这个时候可以通过 addTodoDispatcher("To read.") 或者 todoDispatchers.addTodo("To read.") 触发这个 action。

看起来这个只是省略了一个dispatch调用的代码,官方文档是这么说明的:

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 的场景是当你需要把 action creator 往下传到一个组件上,却不想让这个组件觉察到 Redux 的存在,而且不希望把 Redux storedispatch 传给它。)

也就是说它的主要应用场景是对某个组件隐藏 redux 的存在:

// 不使用 bindActionCreators
// 组件内调用 dispatch(addTodo("sth"))、dispatch(addTodo("sth"))
// 组件需要了解 redux action 的触发方式
<TodoList todos={todos} dispatch={dispatch} addTodo={addTodo} removeTodo={removeTodo}/>

// 使用 bindActionCreators
// 组件内调用 addTodo("sth")、removeTodo("sth")
// 组件不需要知道 redux 的存在,直接调用函数
<TodoList todos={todos} {...todoDispatchers} />

源码注释

去除参数类型检查相关的一些代码后源代码如下:

export default function bindActionCreators(actionCreators, dispatch) {
if (typeof actionCreators === 'function') {
return bindActionCreator(actionCreators, dispatch)
}

// actionCreators 类型不是函数和对象 或者 actionCreators 是空,抛出异常
if (typeof actionCreators !== 'object' || actionCreators === null) {
throw ...
}

var keys = Object.keys(actionCreators)
var boundActionCreators = {}
for (var i = 0; i < keys.length; i++) {
var key = keys[i]
var actionCreator = actionCreators[key]
// 当 actionCreators 类型是对象时
// 返回的对象仅保留其属性值类型为函数的属性,其他类型不保留
if (typeof actionCreator === 'function') {
boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
}
}
return boundActionCreators
}

function bindActionCreator(actionCreator, dispatch) {
return (...args) => dispatch(actionCreator(...args))
}

代码很简单,如果传递的 actionCreators 的类型是函数,则返回内容是一个 dispatcher,即: (...args) => dispatch(actionCreator(...args)),如果传递 actionCreators 的类型是一个对象,则返回一个相似结构的对象,只不过对象的每个属性值都是 dispatcher

简单来说,如果 actionCreators 是 addTodo 返回值为 todo => dispatch(addTodo(todo)), 如果:

// actionCreators 类型是函数
var addTodoDispatcher = bindActionCreators(addTodo, dispatch);
//等价于:
var addTodoDispatcher = todo => dispatch(addTodo(todo));

// actionCreators 类型是对象
var todoDispatchers = bindActionCreators({addTodo, removeTodo, ..., otherTodo}, dispatch);
//等价于:
var todoDispatchers = {
addTodo: todo => dispatch(addTodo(todo)),
removeTodo: todo => dispatch(removeTodo(todo)),
otherTodo: (...args) => dispatch(otherTodo(args)),
}

相关

Redux 源码阅读笔记:

参考

本文阅读代码版本 3.5.2