带你逐行阅读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> 分析:

  1. createStore接收三个参数rootReducer, preloadedState, composedEnhancers
  • rootReducer: 经过combineReducers方法组合后的reducer集合
  • preloadedState:预加载一个state,用来初始化state树,不过此参数较鸡肋,几乎没什么用
  • composedEnhancers:组合后的enhancer集合
  1. 先通过applyMiddleware接入一个中间件列表

  2. 然后把中间件列表作为增强器(enhancers)的一项,和其他增强器组合成一个新的增强器数组

  3. 通过compose方法,组合增强器列表

  4. 传入参数,构建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方法的逻辑:

  1. 置一个订阅标识,用来配合取消订阅方法。

  2. nextListeners队列添加订阅者listener

  3. 返回一个取消订阅方法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的逻辑:

  1. (全时刻)同步更新isDispatching标识,用来同一时刻reduxdispatch一次。

  2. 执行reducer方法,并将返回值赋给currentState

  3. currentListeners更新为nextListeners

  4. 依次通知订阅者。

注意: 之前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方法来梳理:

  1. 用户使用applyMiddleware方法加载中间件时:const middlewareEnhancer = applyMiddleware(...middlewares),执行第一层方法return createStore => (...args) => {...}

  2. creatStore方法接收了enhancer参数后,会返回enhancer(createStore)...的结果,enhancer又是applyMiddleware的组合,故此时进入了applyMiddleware的第二层逻辑

  3. enhancer(createStore)...createStore传递给匿名函数return createStore => (...args) => {...}createStore

  4. 执行enhancer(createStore)(reducer, preloadedState)方法,调用const store = createStore(...args)创建store(绕了半天终于开始真正的createStore了)。

  5. 提取store的部分api(getStatedispatch)传递给中间件链,让中间件这个三层函数执行一次(变成return function(dispatch) {...})。

  6. 通过compose方法从右到左组合中间件链,并执行一次此中间件链,将返回值(return function(action) {...})赋值给dispatchdispatch = compose(...chain)(store.dispatch))。

  7. 最终得到一个会依次执行中间件链方法的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会拿到createStorecurrentState这个状态树和dispathaction。依次执行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拓展各种各样的功能。

带你逐行阅读redux源码的更多相关文章

  1. 带着问题看redux源码

    前言 作为前端状态管理器,这个比较跨时代的工具库redux有很多实现和思想值得我们思考.在深入源码之前,我们可以相关注下一些常见问题,这样带着问题去看实现,也能更加清晰的了解. 常见问题 大概看了下主 ...

  2. redux源码阅读之compose,applyMiddleware

    我的观点是,看别人的源码,不追求一定要能原样造轮子,单纯就是学习知识,对于程序员的提高就足够了.在阅读redux的compose源码之前,我们先学一些前置的知识. redux源码阅读之compose, ...

  3. redux 源码阅读

    目录 [目录结构] [utils] actionTypes.js isPlainObject.js warning.js [逻辑代码] index.js createStore.js compose. ...

  4. 手把手带你阅读Mybatis源码(三)缓存篇

    前言 大家好,这一篇文章是MyBatis系列的最后一篇文章,前面两篇文章:手把手带你阅读Mybatis源码(一)构造篇 和 手把手带你阅读Mybatis源码(二)执行篇,主要说明了MyBatis是如何 ...

  5. 从Redux源码探索最佳实践

    前言 Redux 已经历了几个年头,很多 React 技术栈开发者选用它,我也是其中一员.期间看过数次源码,从最开始为了弄清楚某一部分运行方式来解决一些 Bug,到后来看源码解答我的一些假设性疑问,到 ...

  6. 逐行解读HashMap源码

    [本文版权归微信公众号"代码艺术"(ID:onblog)所有,若是转载请务必保留本段原创声明,违者必究.若是文章有不足之处,欢迎关注微信公众号私信与我进行交流!] 一.写在前面 相 ...

  7. Redux源码分析之createStore

    接着前面的,我们继续,打开createStore.js, 直接看最后, createStore返回的就是一个带着5个方法的对象. return { dispatch, subscribe, getSt ...

  8. 如何阅读jdk源码?

    简介 这篇文章主要讲述jdk本身的源码该如何阅读,关于各种框架的源码阅读我们后面再一起探讨. 笔者认为阅读源码主要包括下面几个步骤. 设定目标 凡事皆有目的,阅读源码也是一样. 从大的方面来说,我们阅 ...

  9. redux源码图解:createStore 和 applyMiddleware

    在研究 redux-saga时,发现自己对 redux middleware 不是太了解,因此,便决定先深入解读一下 redux 源码.跟大多数人一样,发现 redux源码 真的很精简,目录结构如下: ...

随机推荐

  1. UVA11540 Sultan's Chandelier Burnside 引理 + DP

    题目传送门 https://vjudge.net/problem/UVA-11540 https://uva.onlinejudge.org/index.php?option=com_onlineju ...

  2. Graphics 绘图

    Graphics类提供基本绘图方法,Graphics2D类提供更强大的绘图能力. Graphics类提供基本的几何图形绘制方法,主要有:画线段.画矩形.画圆.画带颜色的图形.画椭圆.画圆弧.画多边形等 ...

  3. Docker常规操作

    原创转载请注明出处:https://www.cnblogs.com/agilestyle/p/11601853.html Docker 常⽤命令 镜像相关 • docker pull <imag ...

  4. NetCore中的环境变量的值取自于哪里?

    环境 操作系统 win10 IIS 10 net core 2.2 ,net core 3.0 分别生成了三个环境变量的配置文件: 以及测试代码: public void Configure(IApp ...

  5. webpack打包工具简单案例

    目录结构: 入口文件:main.js 把项目所有的依赖文件都放进main.js //1.使用CommonJs的模块化规范 const {add, mul} = require('./mathUtil. ...

  6. mybatis框架之动态代理

    坦白讲,动态代理在日常工作中真没怎么用过,也少见别人用过,网上见过不少示例,但总觉与装饰模式差别不大,都是对功能的增强,什么前置后置,其实也就那么回事,至于面试中经常被问的mybatis框架mappe ...

  7. 一、生成网络表--create Netlist

    Orcad Capture原理图篇 一.生成网络表--create Netlist 1.操作: .dsn文件--Tools--create Netlist 出现如下对话框--默认不进行更改--点击确定 ...

  8. pytest_用例运行级别_函数级

    '''  函数级(setup_function/teardown_function只对函数用例生 效(不在类中)在类中是用该方法不生效 ''' import pytest def setup_mod ...

  9. VS2010提示error TRK0002: Failed to execute command

    转自VC错误:http://www.vcerror.com/?p=277 问题描述: windows8自动更新Microsoft .NET Framework 3.5和4.5.1安全更新程序,今天用V ...

  10. Linux awk 命令 说明

    一. AWK 说明 awk是一种编程语言,用于在linux/unix下对文本和数据进行处理.数据可以来自标准输入.一个或多个文件,或其它命令的输出.它支持用户自定义函数和动态正则表达式等先进功能,是l ...