带你逐行阅读redux源码
带你逐行阅读redux源码
redux版本:2019-7-17最新版:v4.0.4
git 地址:https://github.com/reduxjs/redux/tree/v4.0.4
redux目录结构
+-- src // redux的核心内容目录
| +-- utils // redux的核心工具库
| | +-- actionTypes.js // 一些默认的随机actionTypes
| | +-- isPlainObject.js // 判断是否是字面变量或者new出来的object
| | +-- warning.js // 打印警告的工具类
| +-- applyMiddleware.js
| +-- bindActionCreator.js
| +-- combineReducers.js
| +-- compose.js
| +-- createStore.js
| +-- index.js
1. index.js
import createStore from './createStore'
import combineReducers from './combineReducers'
import bindActionCreators from './bindActionCreators'
import applyMiddleware from './applyMiddleware'
import compose from './compose'
import warning from './utils/warning'
import __DO_NOT_USE__ActionTypes from './utils/actionTypes'
/*
* 这里定义一个空函数,用来判断环境是否是生产环境且代码被压缩
*/
function isCrushed() {}
if (
process.env.NODE_ENV !== 'production' &&
typeof isCrushed.name === 'string' &&
isCrushed.name !== 'isCrushed'
) {
warning(
'xxx'
)
}
export {
createStore,
combineReducers,
bindActionCreators,
applyMiddleware,
compose,
__DO_NOT_USE__ActionTypes
}
index
的代码非常简洁,主要逻辑就是提取各个目录下的文件并提供统一出口,供外部调用。
isCrushed
空函数很巧妙的利用代码压缩工具的原理对当前环境做了一个判断。
代码压缩工具会将function isCrushed() {}
方法压缩成一个单字方法,像这样function a(){}
,压缩之后isCrushed
方法的name自然也变成了a
,导致接下来的if判断成立,抛出警告。
2. createStore.js
<1> 先看看如何使用
要研究其原理,必先了解其使用,我们先看看怎么初始化一个store
configureStore(preloadedState) {
// 配置中间件
const middlewares = [loggerMiddleware]
const middlewareEnhancer = applyMiddleware(...middlewares)
// 配置增强器
const enhancers = [middlewareEnhancer, monitorReducersEnhancer]
// 组合增强器
const composedEnhancers = compose(...enhancers)
// 创建store
const store = createStore(rootReducer, preloadedState, composedEnhancers)
return store
}
<2> 分析:
createStore
接收三个参数rootReducer, preloadedState, composedEnhancers
- rootReducer: 经过combineReducers方法组合后的reducer集合
- preloadedState:预加载一个state,用来初始化state树,不过此参数较鸡肋,几乎没什么用
- composedEnhancers:组合后的enhancer集合
-
先通过
applyMiddleware
接入一个中间件列表 -
然后把中间件列表作为增强器(
enhancers
)的一项,和其他增强器组合成一个新的增强器数组 -
通过
compose
方法,组合增强器列表 -
传入参数,构建
redux store
这里可以推测出来:中间件肯定是增强器的一个子项,可以把中间件理解为增强器的一部分。
<3> 参数验证:
import $$observable from 'symbol-observable'
import ActionTypes from './utils/actionTypes'
import isPlainObject from './utils/isPlainObject'
/**
* 创建redux仓库
*
* @param {Function} reducer 一个接收当前状态和和action动作处理业务后返回新的状态树的方法
*
* @param {any} [preloadedState] 初始化状态树
*
* @param {Function} [enhancer] store的增强器,用来加强store,可以用来加载中间件
*
* @returns {Store} Redux store,可以获取状态树,分发(dispatch)动作(action)
* 订阅(subscribe)变化
*/
export default function createStore(reducer, preloadedState, enhancer) {
/**
* 这里有四个if检测
*
* 第一个用来提示你不支持直接传入多个enhancer,需要用compose组合enhancer之后再传入
*
* 第二个if用来转换参数,如果createStore只接收两个参数,enhancer的值取第二个参数,
* 这也是为什么我们createStore的时候可以省略掉preloadedState的原因
*
* 第三个if判断enhancer如果存在且不是一个函数,则抛出enhancer类型错误的提示,如果存在
* 则执行enhancer方法
*
* 第四个if对reducer进行类型检测,如果不是函数则抛出错误
*/
if (
(typeof preloadedState === 'function' && typeof enhancer === 'function') ||
(typeof enhancer === 'function' && typeof arguments[3] === 'function')
) {
throw new Error(...)
}
if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
enhancer = preloadedState
preloadedState = undefined
}
if (typeof enhancer !== 'undefined') {
if (typeof enhancer !== 'function') {
throw new Error('Expected the enhancer to be a function.')
}
return enhancer(createStore)(reducer, preloadedState)
}
if (typeof reducer !== 'function') {
throw new Error('Expected the reducer to be a function.')
}
...
}
首先createStore
会在方法执行之前对3个参数进行强类型检测,对不理想的类型进行错误提示。同时对2个参数的情景进行兼容,所以我们在createStore
的时候是可以省略第二个参数preloadedState
。
<4> 方法总览:
export default function createStore(reducer, preloadedState, enhancer) {
let currentReducer = reducer // 当前reducer
let currentState = preloadedState // 当前状态树
let currentListeners = [] // 当前订阅者列表
let nextListeners = currentListeners // 未来订阅者列表
let isDispatching = false // 是否处于分发状态
/**
* 对订阅者进行浅拷贝,在dispatch时生成订阅者的临时列表副本
* This makes a shallow copy of currentListeners so we can use
* nextListeners as a temporary list while dispatching.
*
* 确保nextListeners是可以更新的,保证nextListeners !== currentListeners
* 用来阻止一些中间件产生的bug
*/
function ensureCanMutateNextListeners() {
...
}
/**
* 获取state状态树
*
* @returns {any} 当前的状态树.
*/
function getState() {
...
}
/**
* 订阅方法,在action被dispatch时会通知订阅者,订阅者可以通过getState()方法去获取
* 最新的状态树
*
* @param {Function} listener 监听者,是一个回调函数,会在每次dispatch时执行.
* @returns {Function} 返回一个移除监听的方法.
*/
function subscribe(listener) {
...
}
/**
* 分发(dispatch)动作(action),触发状态树变化的唯一入口
*
*
* @param {Object} action 简单对象
*
* @returns {Object} 返回action本事
*
* 注意:如果你使用了自定义的中间件,可以对dispatch进行包装,使其返回其他对象
*/
function dispatch(action) {
...
}
/**
* 更新reducer,一般用在需要异步路由的大型项目中会使用
*
* @param {Function} nextReducer 新的reducer,会替换掉原来的reducer
* @returns {void}
*/
function replaceReducer(nextReducer) {
...
}
/**
* 提供给其他观察者模式/响应式库的交互操作
* @returns {observable}
* https://github.com/tc39/proposal-observable
*/
function observable() {
...
}
// 分发初始化action,获取所有reducer的初始状态
dispatch({ type: ActionTypes.INIT })
return {
dispatch,
subscribe,
getState,
replaceReducer,
[$$observable]: observable
}
}
createStore
返回一个包含dispatch
,subscribe
,getstate
,replaceReducer
等方法的对象,并会在内部分发一个初始化状态,用来初始化state状态树。
<5> ensureCanMutateNextListeners
function ensureCanMutateNextListeners() {
if (nextListeners === currentListeners) {
nextListeners = currentListeners.slice()
}
}
此方法很简单,浅拷贝一份订阅者列表至nextListeners
。
Array.slice()
方法:从已有的数组中返回选定的元素。不传参就是对原数组进行浅拷贝。
<6> getState
function getState() {
if (isDispatching) {
throw new Error(...)
}
return currentState
}
此方法也很简单,先是一个入口判断,不允许在dispatching
的时候调用。很粗暴的直接返回当前的状态树(state
)。
注意: 因为Store.getState()
获取的就是state
本身,基于js的特性我们甚至可以对此对象进行修改,虽然此时会改变state
的值,但是却不会通知listener
订阅者。所以直接对Store.getState()
是一件极其危险的事情。
<6> subscribe
function subscribe(listener) {
// 入口参数校验,只允许传入function
if (typeof listener !== 'function') {
throw new Error('Expected the listener to be a function.')
}
// 不允许在dispatching时订阅
if (isDispatching) {
throw new Error(...)
}
// 订阅标记,标识listener是否被订阅
let isSubscribed = true
// 保证nextListeners !== currentListeners
ensureCanMutateNextListeners()
/**
* 给nextListeners添加订阅者
* 这里大家可能会有一个疑问,只是把listener赋给了nextListeners,currentListeners并没
* 有变化。那么currentListeners是何时进行同步的呢?别急,后面会有答案。
*/
nextListeners.push(listener)
// 返回值一个取消订阅的函数,这种技巧非常值得我们日常写代码借鉴,避免了我们额外开辟变量去缓存
// 取消订阅的方法
return function unsubscribe() {
// 简单判断,已取消的订阅会被丢弃
if (!isSubscribed) {
return
}
// 不允许在dispatching时取消订阅
if (isDispatching) {
throw new Error(...)
}
// 更新订阅标记,置为false,对应unsubscribe入口的判断方法
isSubscribed = false
// 保证nextListeners !== currentListeners
ensureCanMutateNextListeners()
// 从nextListeners中删除当前订阅者
const index = nextListeners.indexOf(listener)
nextListeners.splice(index, 1)
}
}
理一下subscribe
方法的逻辑:
-
置一个订阅标识,用来配合取消订阅方法。
-
往
nextListeners
队列添加订阅者listener
。 -
返回一个取消订阅方法
unsubscribe
。
注意: 此时currentListeners
队列中并没有最新的监听者listener
。
<7> dispatch
function dispatch(action) {
// isPlainObject是utli里面提供的工具方法,判断action是否是纯粹的对象,dispatch只接收纯粹的对象
// plainObject:通过"{}"或"new Object"创建的对象
if (!isPlainObject(action)) {
throw new Error(...)
}
// action必须包含type属性
if (typeof action.type === 'undefined') {
throw new Error(...)
}
// 同一时刻redux只能dispatch一次
if (isDispatching) {
throw new Error('Reducers may not dispatch actions.')
}
try {
// 将isDispatching标识置为true
isDispatching = true
// 执行reducer方法,并将执行后的结果返回给currentState树
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// 结合combineReducers.js 174行分析
// currentReducer执行后 会返回一个按需更新的state树并返回给currentState
// 保证了store.getState每次都能拿到**当时**的state树
// 便于数据追踪
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
currentState = currentReducer(currentState, action)
} finally {
// 将isDispatching标识置为false
isDispatching = false
}
// 先将nextListeners赋值给currentListeners,然后按队列顺序依次通知订阅者
const listeners = (currentListeners = nextListeners)
for (let i = 0; i < listeners.length; i++) {
const listener = listeners[i]
listener()
}
// 将action原样返回
return action
}
dispatch的逻辑:
-
(全时刻)同步更新
isDispatching
标识,用来同一时刻redux
只dispatch
一次。 -
执行
reducer
方法,并将返回值赋给currentState
。 -
将
currentListeners
更新为nextListeners
。 -
依次通知订阅者。
注意: 之前subscribe
方法没有同步currentListeners
的事情,在dispatch
里面做了。这就解决了之前currentListeners
不是完整的订阅者列表的问题。
<8> replaceReducer
function replaceReducer(nextReducer) {
// 类型判断
if (typeof nextReducer !== 'function') {
throw new Error(...)
}
// 直接将新的reducer赋值给当前reducer,很简单粗暴
currentReducer = nextReducer
// 通知一个更新状态,效果与ActionTypes.INIT类似
dispatch({ type: ActionTypes.REPLACE })
}
更新reducer,直接用新的reducer覆盖旧的reducer。有些大型项目做了拆分,需要异步加载一些reducer,就会用到此方法替换reducer。一般项目可能很少用。
<9> observable
function observable() {
const outerSubscribe = subscribe
return {
subscribe(observer) {
// 参数验证观察者只能是对象
if (typeof observer !== 'object' || observer === null) {
throw new TypeError('Expected the observer to be an object.')
}
// 这里是observer通用的规范,用next获取下一次的状态
function observeState() {
if (observer.next) {
observer.next(getState())
}
}
observeState()
// 订阅观察者,并将取消订阅返回
const unsubscribe = outerSubscribe(observeState)
return { unsubscribe }
},
// $$observable是一个es6的symbol定义,用来标识此变量是observable对象
[$$observable]() {
return this
}
}
}
observable日常开发不会用到,一般第三方observe库才会用到。例如redux-observable等rxjs库
3. compose.js
/**
* 组合多个函数
* 从右到左组合函数,最右边的函数能接收多个参数,然后依次将右侧函数的执行结果作为参数
* 传给左边的函数
*
* @param {...Function}
* @returns {Function}
*/
export default function compose(...funcs) {
// 没有参数时直接返回一个返回入参的函数
if (funcs.length === 0) {
return arg => arg
}
// 只有一个参数时,直接返回此函数
if (funcs.length === 1) {
return funcs[0]
}
// 多个参数时,会返回一个从右至左依次执行函数的函数
// Array.reduce()方法会循环数组并将上一次计算的结果作为第一个参数(a)返回给下一次循环
return funcs.reduce((a, b) => (...args) => a(b(...args)))
}
compose
方法非常简短,但是理解起来并不简单,运用了科里化
的思想,通过es6的语法糖实现了
一行代码完成函数的层层传递功能。
redux
洋葱模型式的中间件核心就是通过这个compose
方法层层封装(增强)dispatch
(dispatch作为修改state的唯一入口,一切state必将流入dispatch)实现的。
4. applyMiddleware.js
<1> 先看看如何使用
官方的一个日志中间件的例子:
const logger = store => next => action => {
console.group(action.type)
console.info('dispatching', action)
let result = next(action)
console.log('next state', store.getState())
console.groupEnd()
return result
}
export default logger
<2> 分析
这个中间件会在每一次dispatch
开始和结束的时候进行打印,由于洋葱模型的特性,这个中间
件建议放在最右边。
观察中间件的结构,可以看出来中间件的结构其实是一个三层函数的封装。
function (store) {
return function(dispatch) {
return function(action) {
...
}
}
}
至于为什么中间件要固定这么写,待会我们从applyMiddleware
的源码进行分析。
<3> 源码解读
import compose from './compose'
/**
* 创建中间件去增强dispatch的功能,比如日志记录,异步操作dispatch等
*
* @param {...Function} middlewares 中间件链
* @returns {Function} store enhancer
*/
export default function applyMiddleware(...middlewares) {
// 返回一个两层函数
// 第一层以createStore作为参数的函数
// 第二层是createStore方法需要接收的参数
return createStore => (...args) => {
// 调用createStore方法创建store
const store = createStore(...args)
// 常规操作,给dispatch一个不允许执行的默认值
let dispatch = () => {
throw new Error(...)
}
// 提取store部分api作为临时参数
const middlewareAPI = {
getState: store.getState,
dispatch: (...args) => dispatch(...args)
}
// 依次执行一次中间件链
const chain = middlewares.map(middleware => middleware(middlewareAPI))
// 将dispatch重新赋值为被中间件处理过后的dispatch
dispatch = compose(...chain)(store.dispatch)
// 更新store的dispatch
return {
...store,
dispatch
}
}
}
<4> applyMiddleware流程
这里需要结合creatStore
方法来梳理:
-
用户使用
applyMiddleware
方法加载中间件时:const middlewareEnhancer = applyMiddleware(...middlewares)
,执行第一层方法return createStore => (...args) => {...}
-
在
creatStore
方法接收了enhancer
参数后,会返回enhancer(createStore)...
的结果,enhancer
又是applyMiddleware
的组合,故此时进入了applyMiddleware
的第二层逻辑 -
将
enhancer(createStore)...
的createStore
传递给匿名函数return createStore => (...args) => {...}
的createStore
。 -
执行
enhancer(createStore)(reducer, preloadedState)
方法,调用const store = createStore(...args)
创建store(绕了半天终于开始真正的createStore
了)。 -
提取store的部分api(
getState
,dispatch
)传递给中间件链,让中间件这个三层函数执行一次(变成return function(dispatch) {...}
)。 -
通过
compose
方法从右到左组合中间件链,并执行一次此中间件链,将返回值(return function(action) {...}
)赋值给dispatch
(dispatch = compose(...chain)(store.dispatch)
)。 -
最终得到一个会依次执行中间件链方法的dispatch,并把dispatch更新给store返回。
这里运用了大量科里化的思想,读起来难免有些绕,需要来回反复模拟函数执行的顺序才能明白其中的奥妙。
注意:
这里有一点同学们可能会疑惑
let dispatch = () => {
throw new Error(...)
}
const middlewareAPI = {
getState: store.getState,
dispatch: (...args) => dispatch(...args)
}
...
dispatch = compose(...chain)(store.dispatch)
dispatch默认给的是一个抛错的函数,而且在(...args) => dispatch(...args)
这一行调用时,
dispatch并没有更新,而是在最后dispatch = compose(...chain)(store.dispatch)
才更新的。
那么,dispatch在执行时到底会不会抛错呢?
答案是: 不会
这就是(...args) => dispatch(...args)
方法的巧妙之处了。如果此时直接定义dispatch: dispatch
的话middlewareAPI.dispatch
就会直接指向() => { throw new Error(...) }
函数,并且不会再修改。
但是dispatch: (...args) => dispatch(...args)
这一行其实只是做了一个新函数的定义,并没有真正的指向() => { throw new Error(...) }
函数。而是指向dispatch
本身。所以dispatch
即使在后期更新,(...args) => dispatch(...args)
方法内的dispatch
也会同步更新。这种奇技淫巧是不是很神奇。
可以看chrome的例子:
5. combineReducers.js
<1> getUndefinedStateErrorMessage
// 如果reducer执行后没有返回值,会抛出异常
function getUndefinedStateErrorMessage(key, action) {
const actionType = action && action.type
const actionDescription =
(actionType && `action "${String(actionType)}"`) || 'an action'
return (
`Given ${actionDescription}, reducer "${key}" returned undefined. ` +
`To ignore an action, you must explicitly return the previous state. ` +
`If you want this reducer to hold no value, you can return null instead of undefined.`
)
}
这行代码很明显,直接根据actiton抛出对应错误提示,不允许reducer的返回值为undefined.
<2> getUnexpectedStateShapeWarningMessage
// 错误检测
function getUnexpectedStateShapeWarningMessage(
inputState,
reducers,
action,
unexpectedKeyCache
) {
const reducerKeys = Object.keys(reducers)
const argumentName =
action && action.type === ActionTypes.INIT
? 'preloadedState argument passed to createStore'
: 'previous state received by the reducer'
if (reducerKeys.length === 0) {
return (
'Store does not have a valid reducer. Make sure the argument passed ' +
'to combineReducers is an object whose values are reducers.'
)
}
if (!isPlainObject(inputState)) {
return (
`The ${argumentName} has unexpected type of "` +
{}.toString.call(inputState).match(/\s([a-z|A-Z]+)/)[1] +
`". Expected argument to be an object with the following ` +
`keys: "${reducerKeys.join('", "')}"`
)
}
const unexpectedKeys = Object.keys(inputState).filter(
key => !reducers.hasOwnProperty(key) && !unexpectedKeyCache[key]
)
unexpectedKeys.forEach(key => {
unexpectedKeyCache[key] = true
})
if (action && action.type === ActionTypes.REPLACE) return
if (unexpectedKeys.length > 0) {
return (
`Unexpected ${unexpectedKeys.length > 1 ? 'keys' : 'key'} ` +
`"${unexpectedKeys.join('", "')}" found in ${argumentName}. ` +
`Expected to find one of the known reducer keys instead: ` +
`"${reducerKeys.join('", "')}". Unexpected keys will be ignored.`
)
}
}
<3> assertReducerShape
// reducer错误检测
// 1. 确保初始化后有值
// 2. 确保执行任何action后都有返回值
function assertReducerShape(reducers) {
Object.keys(reducers).forEach(key => {
const reducer = reducers[key]
const initialState = reducer(undefined, { type: ActionTypes.INIT })
if (typeof initialState === 'undefined') {
throw new Error(
`Reducer "${key}" returned undefined during initialization. ` +
`If the state passed to the reducer is undefined, you must ` +
`explicitly return the initial state. The initial state may ` +
`not be undefined. If you don't want to set a value for this reducer, ` +
`you can use null instead of undefined.`
)
}
if (
typeof reducer(undefined, {
type: ActionTypes.PROBE_UNKNOWN_ACTION()
}) === 'undefined'
) {
throw new Error(
`Reducer "${key}" returned undefined when probed with a random type. ` +
`Don't try to handle ${ActionTypes.INIT} or other actions in "redux/*" ` +
`namespace. They are considered private. Instead, you must return the ` +
`current state for any unknown actions, unless it is undefined, ` +
`in which case you must return the initial state, regardless of the ` +
`action type. The initial state may not be undefined, but can be null.`
)
}
})
}
<4> combineReducers
/**
* 整合reducers
*
* @param {Object} reducers reducers对象字典
* ex:
* {
* reducerA: function A() {}
* }
* reducerA即reducers的key
* @returns {Function} reducers集合
*/
export default function combineReducers(reducers) {
// 获取reducers的key数组
const reducerKeys = Object.keys(reducers)
// 过滤不合法的reducers, 将合法的reducers重新更新到finalReducers里
const finalReducers = {}
for (let i = 0; i < reducerKeys.length; i++) {
const key = reducerKeys[i]
if (process.env.NODE_ENV !== 'production') {
if (typeof reducers[key] === 'undefined') {
warning(`No reducer provided for key "${key}"`)
}
}
if (typeof reducers[key] === 'function') {
finalReducers[key] = reducers[key]
}
}
const finalReducerKeys = Object.keys(finalReducers)
// 确保不会对同一个key报两次警告
let unexpectedKeyCache
if (process.env.NODE_ENV !== 'production') {
unexpectedKeyCache = {}
}
// 获取reducer的错误
let shapeAssertionError
try {
assertReducerShape(finalReducers)
} catch (e) {
shapeAssertionError = e
}
// 返回一个大的reducer
// state:createStore里的currentState
// action:dispatch的action
// 未来的某个时间(dispatching),这个combination函数会被执行
return function combination(state = {}, action) {
if (shapeAssertionError) {
throw shapeAssertionError
}
// 错误检测
if (process.env.NODE_ENV !== 'production') {
const warningMessage = getUnexpectedStateShapeWarningMessage(
state,
finalReducers,
action,
unexpectedKeyCache
)
if (warningMessage) {
warning(warningMessage)
}
}
// 标识:是否产生了变化
let hasChanged = false
const nextState = {}
for (let i = 0; i < finalReducerKeys.length; i++) {
const key = finalReducerKeys[i]
const reducer = finalReducers[key]
const previousStateForKey = state[key]
// 传入当前执行的reducer的state和action
const nextStateForKey = reducer(previousStateForKey, action)
// 不允许执行结果为undefined
if (typeof nextStateForKey === 'undefined') {
const errorMessage = getUndefinedStateErrorMessage(key, action)
throw new Error(errorMessage)
}
nextState[key] = nextStateForKey
// 判断数据是否有更新,更新就返回新状态,不更新返回旧状态
// 因为reducer的通用逻辑是如果对action没有处理,都会默认返回传入的state,
// 所以这里可以直接使用!==来判断是否相等,引用相等即reducer没有处理任何业务。
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// 结合createStore.js 204行分析
// 此处判断被执行过reducer的state是否更新,如果更新就用nextState,否则直接使用原始state
// 从性能方面考虑
// reducer default 应该返回原state,而不是一个新对象
// 从数据追踪方面考虑
// reducer case 必须返回一个新state,而不是直接在旧state上做修改
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
hasChanged = hasChanged || nextStateForKey !== previousStateForKey
}
return hasChanged ? nextState : state
}
}
combineReducers
先是会执行三个错误检测,确保方法的健壮性。
最终返回一个combination
函数,每次dispatch
其实实质上就是执行此函数。
combination
会拿到createStore
的currentState
这个状态树和dispath
的action
。依次执行reducers
集合里的合法reducer
。并返回一个新的nextState
状态树给store
。
注意:
dispatch
每次执行都会 全量遍历执行 reducer
(感觉会不会造成性能浪费?)。reducer
只匹配action。所以不同的reducer
也可以处理同一个action
执行不同的业务逻辑。
6. bindActionCreators.js
<1> bindActionCreator
// 提供一个绑定action的方法,将dispatch动作内置
// actionCreator: 纯函数
// function updateList(listData) {
// return {
// type: 'updateList',
// data: listData
// }
// }
function bindActionCreator(actionCreator, dispatch) {
return function() {
return dispatch(actionCreator.apply(this, arguments))
}
}
<2> bindActionCreators
/**
* 将actionCreators集合转换成bindActionCreator的集合
*
* @param {Function|Object} actionCreators
*
* @param {Function} dispatch store.dispatch
*
* @returns {Function|Object}
*/
export default function bindActionCreators(actionCreators, dispatch) {
// actionCreators为函数时直接返回bindActionCreator方法
if (typeof actionCreators === 'function') {
return bindActionCreator(actionCreators, dispatch)
}
// actionCreators不合法时报错
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"?`
)
}
// 遍历actionCreators,对每一个actionCreator进行bindActionCreator包装。
const boundActionCreators = {}
for (const key in actionCreators) {
const actionCreator = actionCreators[key]
if (typeof actionCreator === 'function') {
boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
}
}
return boundActionCreators
}
bindActionCreators
是一个辅助方法,能够让我们以方法的形式来调用action
。自动dispatch
对应的action
。
实质上它只是做了这么一个操作 bindActionFoo = (...args) => dispatch(actionCreator(...args))
bindActionCreators
其实在实际使用中并不一定会用到,惟一会使用到 bindActionCreators 的场景是当你需要把 action creator 往下传到一个组件上,却不想让这个组件觉察到 Redux 的存在,而且不希望把 dispatch 或 Redux store 传给它。
回顾
redux代码短小精悍,大量集成了科里化思想,读起来可谓绕之又绕。但是通过反复调试追踪,还是可以理清楚作者的思路。
其中核心思想
其一是集中式的订阅发布者模式,通过唯一入口dispatch
来更新store state
树并通知listeners
。
其二是applyMiddleware
通过多层函数封装实现洋葱模型式的dispatch
增强器功能。让使用者们可以为dispatch
拓展各种各样的功能。