前言
众所周知,redux是react里面难度比较高,也比较重要的的一个知识点,这也是面试的高频面试题之一。那么不管为了自我的一个提升,还是为了面试准备,redux源码的学习都是必不可少。另外,源码的学习从来不是一蹴而就,需要多次的重温以及结合实际来练习,才会让你记忆更加深刻。下面我们就开始学习相关的知识吧。
redux的基本执行流程
以下是三个redux执行流程的图,要是对redux执行流程仍然不太清楚的同学,可以参照看看,希望对你们有帮助
目录分析
redux源码使用ts写的,但是我们为了学习redux,没有必要过多关注与功能无关的一些知识,所以我是把编译后redux的代码拿过来直接学习。
这里我们无需关心utils里的方法,我们只需要关注createStore,combineReducer,applyMiddleware,bindActionCreators,compose这五个文件。其中我们需要重点关注的是前三个文件。
首先我们来看createStore
createStore
import $$observable from './utils/symbol-observable'
import ActionTypes from './utils/actionTypes'
import isPlainObject from './utils/isPlainObject'
import { kindOf } from './utils/kindOf'
/**
* 创建一个redux的store来保存state的状态树
* 要改变store唯一的方法就是调用dispatch()
* 你的应用里面只有一个store,你的应用里面里面可能有很多的模块,你可以有很多个reducer,最后在root reducer中使用combineReducers方法把他们合并起来。
* @param {Function} reducer type:function
* 这个函数会接受当前state树的状态,返回下一个state树的状态。
* @param {any} [preloadedState] type:undefined | object
* 初始化的state。如果是你在有服务端渲染的app使用,你可以把服务端的状态放进初始化的state中。或者你可以恢复以前的状态
* @param {Function} [enhancer] type:undefined | function
* store的增强器。你可以使用第三方的一些插件来增强存储,时间旅行,持久化等功能。
* Redux自带的唯一可以增强的方就是applyMiddleware()
* @returns {Store} 返回值 就是一个store
*
*/
export default function createStore(reducer, preloadedState, enhancer) {
//如果传递的preloadedState和enhancer都是函数,而不是对象的话,或者enhancer有多个的话
//那么就抛出一个异常
if (
(typeof preloadedState === 'function' && typeof enhancer === 'function') ||
(typeof enhancer === 'function' && typeof arguments[3] === 'function')
) {
throw new Error(
'It looks like you are passing several store enhancers to ' +
'createStore(). This is not supported. Instead, compose them ' +
'together to a single function. See https://redux.js.org/tutorials/fundamentals/part-4-store#creating-a-store-with-enhancers for an example.'
)
}
//如果preloadedState是函数且enhancer是undefined
//那么就认为没有传递preloadedState,就把preloadedState赋值给enhancer,把undefined赋值给preloadedState
if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
enhancer = preloadedState
preloadedState = undefined
}
//如果enhancer不等于undefined但也不是函数,那就抛出异常。enhancer的类型是undefined或者function
// 如果enhancer存在,那他必须是个function, 否则throw Error
if (typeof enhancer !== 'undefined') {
if (typeof enhancer !== 'function') {
throw new Error(
`Expected the enhancer to be a function. Instead, received: '${kindOf(
enhancer
)}'`
)
}
/**
* 传入符合参数类型的参数,就可以执行 enhancer,
* 但是这个return深深的吸引了我, 因为说明有applyMiddleware的时候后面的都不用看了 ??? 当然不可能
* 可是applyMiddleware其实是必用项,所以猜想一下applyMiddleware强化store之后会enhancer赋值undefined,再次调用createStore
* 上下打个debugger看一下执行顺序(debugger位置以注释),果然不出所料
* 好了, 假设我们还不知道applyMiddleware()这个funcrion具体干了什么,
* 只知道他做了一些处理然后重新调用了createStore并且enhancer参数为undefined
* 先记下,后续在看applyMiddleware, 因为我们现在要看的是createStore
* * */
return enhancer(createStore)(reducer, preloadedState)
}
//如果enhancer不存在
if (typeof reducer !== 'function') {
throw new Error(
`Expected the root reducer to be a function. Instead, received: '${kindOf(
reducer
)}'`
)
}
let currentReducer = reducer //当前的reducer
let currentState = preloadedState //初始化当前的state
let currentListeners = []// 当前有哪些监听器
let nextListeners = currentListeners
let isDispatching = false // 标志dispatch是否正在执行中
/**
* 这将创建currentListeners的浅拷贝,以便我们可以在dispatch时候,nextListeners作为临时列表。
* 这可以防止消费者dispatch时出现任何错误在调度的中间订阅/取消订阅。
* 其实这里是保存一份订阅快照
*/
function ensureCanMutateNextListeners() {
// 不要忘了let nextListeners = currentListeners // 浅拷贝下这个队列
// 判断nextListeners和当前的currentListeners是不是一个引用
if (nextListeners === currentListeners) {
// 如果是一个引用的话深拷贝出来一个currentListeners赋值给nextListener
nextListeners = currentListeners.slice()
}
}
/**
* 读取状态树
* @returns {any} 返回值是当前的状态树
*/
function getState() {
if (isDispatching) {
throw new Error(
'You may not call store.getState() while the reducer is executing. ' +
'The reducer has already received the state as an argument. ' +
'Pass it down from the top reducer instead of reading it from the store.'
)
}
return currentState
}
/**
* @param {Function} listener 每次dispatch的函数
* @returns {Function} 返回值是移除监听器的函数
*
* 订阅listener监听器。
* 它会在dispatch一个action或者状态树被改变的情况下被调用。你可以调用getState()这个方法去读取当前的状态树
* 你可以在一个要改变的listener监听器中调用一个dispatch函数
* 以下是注意事项:
* 1、订阅是在每次调用dispatch()方法之前进行快照的。如果在调用监听器时,订阅或者取消订阅,都不会对当前进行的dispatch产生任何影响
* 但是,对于下一个dispatch,不论是否嵌套或者其它的一些条件,都会使用订阅列表的最近的快照。
* 2、监听器不应该期望看到偶有的状态都发生变更。因为在调用监听器前,在嵌套的dispatch()中,状态可能已经多次进行改变了。
* 但是,可以保证dispatch 启动之前注册的所有订阅者在退出时都将会以最新的状态调用。
*/
// store.subscribe方法设置监听函数,一旦触发dispatch,就自动执行这个函数
// listener是一个callback function
function subscribe(listener) {
if (typeof listener !== 'function') {
throw new Error(
`Expected the listener to be a function. Instead, received: '${kindOf(
listener
)}'`
)
}
if (isDispatching) {
throw new Error(
'You may not call store.subscribe() while the reducer is executing. ' +
'If you would like to be notified after the store has been updated, subscribe from a ' +
'component and invoke store.getState() in the callback to access the latest state. ' +
'See https://redux.js.org/api/store#subscribelistener for more details.'
)
}
let isSubscribed = true//标志是否已经订阅
ensureCanMutateNextListeners() //把当前的监听器浅拷贝一份nextListeners
nextListeners.push(listener)
//返回移除监听器的函数
return function unsubscribe() {
if (!isSubscribed) {
return
}
if (isDispatching) {
throw new Error(
'You may not unsubscribe from a store listener while the reducer is executing. ' +
'See https://redux.js.org/api/store#subscribelistener for more details.'
)
}
isSubscribed = false //取消已经订阅的标志
ensureCanMutateNextListeners()//把当前的监听器浅拷贝一份nextListeners
//从这个列表中删除这个监听器
const index = nextListeners.indexOf(listener)
nextListeners.splice(index, 1)
currentListeners = null
}
}
/**
* 抛发一个action 这是触发状态更改唯一的方法
* 这个reducer函数会使用当前的状态树,以及会调用给定的action。它的返回值将会通知下state树的下一个状态并且它会改变这个监听器。
* 目前只支持普通对象操作,如果需要抛出promise或者Observable,你需要引入第三方的的中间件去包裹。
* @param {Object} action 它表示更改的纯对象,它可以保持操作的序列化,也可以配合devTools做时间旅行
* @returns {Object} 为方便起见,返回值与你操作的action相同
*/
function dispatch(action) {
// 看下util的isPlainObject
// acticon必须是由Object构造的函数, 否则throw Error
if (!isPlainObject(action)) {
throw new Error(
`Actions must be plain objects. Instead, the actual type was: '${kindOf(
action
)}'. You may need to add middleware to your store setup to handle dispatching other values, such as 'redux-thunk' to handle dispatching functions. See https://redux.js.org/tutorials/fundamentals/part-4-store#middleware and https://redux.js.org/tutorials/fundamentals/part-6-async-logic#using-the-redux-thunk-middleware for examples.`
)
}
if (typeof action.type === 'undefined') {
throw new Error(
'Actions may not have an undefined "type" property. You may have misspelled an action type string constant.'
)
}
if (isDispatching) {
throw new Error('Reducers may not dispatch actions.')
}
try {
isDispatching = true //首先更改是否在dispatch的状态
//执行reducer去更改state,处理完这个state之后,就返回状态树,并赋值给currentState
currentState = currentReducer(currentState, action)
} finally {
isDispatching = false
}
//状态改变之后,先去把nextListeners赋值给currentListeners,
// 然后遍历执行每一个监听器,每个监听器都是一个函数。
// 监听队列
// 所有的的监听函数赋值给 listeners
const listeners = (currentListeners = nextListeners)
for (let i = 0; i < listeners.length; i++) {
const listener = listeners[i]
listener()
}
// 到这里dispatch方法就结束了, 我们来思考总结一下, 为什么要用listeners
// 当dispatch发送一个规范的action时,会更新state
// 但是state改变了之后我们需要做一些事情, 比如更新ui既数据驱动视图
// (当然一般我们使用react,react-redux的时候, 他们会帮我们完成这些事情)
// 所以要提供一个监听模式,当然还要有一个监听函数subscribe, 保证dispatch和subscribe之间的一对多的模式
return action
}
/**
* 用来替换当前计算的store的reducer
* redux 热加载机制的时候用到这个函数
* @param {Function} nextReducer 下一个reducer
* @returns {void}
*/
function replaceReducer(nextReducer) {
//如果nextReducer不是函数,那就抛出异常。
if (typeof nextReducer !== 'function') {
throw new Error(
`Expected the nextReducer to be a function. Instead, received: '${kindOf(
nextReducer
)}`
)
}
currentReducer = nextReducer
//此操作的效果与ActionType.INIT类似。
// 新rootReducer和旧rootReducer中存在的任何reducer都将接收先前的状态。这将使用旧状态树中的任何相关数据有效地填充新状态树。
dispatch({ type: ActionTypes.REPLACE })
}
function observable() {
const outerSubscribe = subscribe
return {
/**
* 最小的可观测的方法
* observer参数是可以用作观察者的一个对象
* 这个observer独享应该有一个next的方法
* 返回值是一个可以用于从store中去取消订阅可观察内容,并且可以防止进一步从可观察对象发射值的对象
*/
subscribe(observer) {
if (typeof observer !== 'object' || observer === null) {
throw new TypeError(
`Expected the observer to be an object. Instead, received: '${kindOf(
observer
)}'`
)
}
function observeState() {
if (observer.next) {
observer.next(getState())
}
}
observeState()
const unsubscribe = outerSubscribe(observeState)
return { unsubscribe }
},
[$$observable]() {
return this
},
}
}
// 创建存储时,将调度一个“INIT”的action,以便reducer返回其初始状态。这将有效地填充初始状态树。
// 有没有想过,在使用redux的时候, 初始化的state哪来的
// 当然是自己先dispatch了一下
//reducer 返回其初始状态
//初始化 store 里的 state tree
dispatch({ type: ActionTypes.INIT })
return {
dispatch,
subscribe,
getState,
replaceReducer,
[$$observable]: observable,
}
}
combineReducer
import ActionTypes from './utils/actionTypes'
import warning from './utils/warning'
import isPlainObject from './utils/isPlainObject'
import { kindOf } from './utils/kindOf'
//不看 与功能无关
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 "${kindOf(
inputState
)}". 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.`
)
}
}
//不看 与功能无关
function assertReducerShape(reducers) {
Object.keys(reducers).forEach((key) => {
const reducer = reducers[key]
// reducer返回值
const initialState = reducer(undefined, { type: ActionTypes.INIT })
if (typeof initialState === 'undefined') {
throw new Error(
`The slice reducer for key "${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.`
)
}
// 很明显assertReducerShape是用于reducer的规范
// 回到combineReducers
if (
typeof reducer(undefined, {
type: ActionTypes.PROBE_UNKNOWN_ACTION(),
}) === 'undefined'
) {
throw new Error(
`The slice reducer for key "${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.`
)
}
})
}
// 用于合并reducer 一般是这样combineReducers({a,b,c})
export default function combineReducers(reducers) {
// reducers中key的数组
const reducerKeys = Object.keys(reducers)
// 最终的reducer
const finalReducers = {}
for (let i = 0; i < reducerKeys.length; i++) {
// 接受当前的key
const key = reducerKeys[i]
// 如果不是生产环境, 当前的reducer是undefined会给出warning
if (process.env.NODE_ENV !== 'production') {
if (typeof reducers[key] === 'undefined') {
warning(`No reducer provided for key "${key}"`)
}
}
// reducer要是一个function
if (typeof reducers[key] === 'function') {
// 赋值给finalReducers
finalReducers[key] = reducers[key]
}
// 循环结束, 目的为了给finalReducers赋值, 过虑了不符合规范的reudcer
}
// 符合规范的reducer的key数组
const finalReducerKeys = Object.keys(finalReducers)
// This is used to make sure we don't warn about the same
// keys multiple times.
// 意想不到的key, 先往下看看
let unexpectedKeyCache
if (process.env.NODE_ENV !== 'production') {
// production环境为{}
unexpectedKeyCache = {}
}
let shapeAssertionError
try {
// 看这个function
assertReducerShape(finalReducers)
} catch (e) {
shapeAssertionError = e
}
// 返回function, 这就是createstore中的reducer参数,reducer参数也就是currentreducer参数
// 自然有state和action两个参数, 可以回createstore文件看看currentReducer(currentState, action)
return function combination(state = {}, action) {
// reducer不规范报错
if (shapeAssertionError) {
throw shapeAssertionError
}
// 比较细致的❌信息,顺便看了一下getUndefinedStateErrorMessage,都是用于提示warning和error的, 不过多解释了
if (process.env.NODE_ENV !== 'production') {
const warningMessage = getUnexpectedStateShapeWarningMessage(
state,
finalReducers,
action,
unexpectedKeyCache
)
if (warningMessage) {
warning(warningMessage)
}
}
let hasChanged = false
const nextState = {} //保存新state的对象
for (let i = 0; i < finalReducerKeys.length; i++) {
// 获取finalReducerKeys的key和value(function)
const key = finalReducerKeys[i]
const reducer = finalReducers[key]
// 当前key的state值
const previousStateForKey = state[key]
// 执行reducer, 返回执行reducer完毕之后新的state
const nextStateForKey = reducer(previousStateForKey, action)
// 不存在返回值报错
if (typeof nextStateForKey === 'undefined') {
const actionType = action && action.type
throw new Error(
`When called with an action of type ${
actionType ? `"${String(actionType)}"` : '(unknown type)'
}, the slice reducer for key "${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.`
)
}
// 新的state放在nextState对应的key里
nextState[key] = nextStateForKey
// 判断新的state是不是同一引用, 以检验reducer是不是纯函数
hasChanged = hasChanged || nextStateForKey !== previousStateForKey
}
//hasChanged如果为false,判断最终合并的reducer的key的数量是不是与state中key的数量是不是相等
hasChanged =
hasChanged || finalReducerKeys.length !== Object.keys(state).length
//如果hasChanged存在就返回下一个state,如果不存在,就返回当前的state
return hasChanged ? nextState : state
}
}
bindActionCreator
import { kindOf } from './utils/kindOf'
function bindActionCreator(actionCreator, dispatch) {
// 闭包
return function () {
// 执行后返回结果为传入的actionCreator直接调用arguments
return dispatch(actionCreator.apply(this, arguments))
}
}
/**
* @export
* @param {*} actionCreators 一个 action creator,或者一个 value 是 action creator 的对象。
* @param {*} dispatch 一个由 Store 实例提供的 dispatch 函数。
* @returns 一个与原对象类似的对象,只不过这个对象的 value 都是会直接 dispatch 原 action creator 返回的结果的函数。
* 如果传入一个单独的函数作为 actionCreators,那么返回的结果也是一个单独的函数。
*
* 场景: 惟一会使用到 bindActionCreators 的场景是当你需要把 action creator 往下传到一个组件上,
* 却不想让这个组件觉察到 Redux 的存在,而且不希望把 dispatch 或 Redux store 传给它。
**/
export default function bindActionCreators(actionCreators, dispatch) {
// actionCreators为function
if (typeof actionCreators === 'function') {
return bindActionCreator(actionCreators, dispatch)
}
if (typeof actionCreators !== 'object' || actionCreators === null) {
throw new Error(
`bindActionCreators expected an object or a function, but instead received: '${kindOf(
actionCreators
)}'. ` +
`Did you write "import ActionCreators from" instead of "import * as ActionCreators from"?`
)
}
const boundActionCreators = {}
//遍历整个actionCreators
//如果item是function类型,那么就调用bindActionCreator方法
for (const key in actionCreators) {
const actionCreator = actionCreators[key]
if (typeof actionCreator === 'function') {
boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
}
}
return boundActionCreators
}
compose
因为后面的applyMiddleware用到了compose方法,所以我们先看compose,最后再看applyMiddleware
/**
* Composes single-argument functions from right to left. The rightmost
* function can take multiple arguments as it provides the signature for
* the resulting composite function.
*
* @param {...Function} funcs The functions to compose.
* @returns {Function} A function obtained by composing the argument functions
* from right to left. For example, compose(f, g, h) is identical to doing
* (...args) => f(g(h(...args))).
*/
//组合函数,将函数串联起来从右到左执行
//compose(funcA, funcB, funcC) === compose(funcA(funcB(funcC())))
export default function compose(...funcs) {
//如果传入函数数组为空,就直接返回一个空的函数
if (funcs.length === 0) {
return (arg) => arg
}
//如果传入函数数组只有一个函数,就直接返回它函数
if (funcs.length === 1) {
return funcs[0]
}
//自己展开 更容易看懂,其实就是科里化函数
return funcs.reduce((a,b)=>{
return (...args)=>{
return a(b(...args));
}
})
//官方实现
// return funcs.reduce((a, b) => (...args) => a(b(...args)))
}
appleMiddleware
import compose from './compose'
/***
*
* middleware就是中间件,简单说在redux中作为扩展 dispatch 的唯一标准的方式。
* 不熟悉的同学自行去api了解一下, 大致结构是这样的
* middleware = (store) => (next) =>(action) =>{ [return next(action)]}
*/
// applyMiddleware用来添加中间件,在修改数据的时候redux通过改造dispatch来实现中间件.
export default function applyMiddleware(...middlewares) {
// 返回一个名为createStore的function
// 不知道你还是否记得createStore.js开头的这段代码
/*
if (typeof enhancer !== 'undefined') {
if (typeof enhancer !== 'function') {
throw new Error('Expected the enhancer to be a function.')
}
return enhancer(createStore)(reducer, preloadedState)
}
嗯哼?对上了吧, 有applyMiddleware的时候直接先执行这里, 没绕过来的同学debugger一下
* */
/**
* createStore.js
* d1 = createStore(reducer, initstate, enhancer){ ... debugger if (typeof enhancer !== 'undefined')}
*
* d2 = if (typeof enhancer !== 'undefined') {
if (typeof enhancer !== 'function') {
throw new Error('Expected the enhancer to be a function.')
}
debugger
return enhancer(createStore)(reducer, preloadedState)
}
* d3 = if (typeof enhancer !== 'undefined') {} debugger
*
* d4 = ... debugger const middlewareAPI = {
// copy getState
getState: store.getState,
dispatch: (...args) => dispatch(...args)
}
d5 = ... debugger const store = createStore(...args)...
*
* 执行顺序
* 创建store的首先是调用createStore(...applyMiddleware()) 大致发生了这样的流程
* createStore(...applyMiddleware()) -> applyMiddleware() -> return function -> d1 -> d2
* 接下来
* return enhancer(createStore)(reducer, preloadedState) -> d5 -> createStore(...args)再次调用createStore -> d1
* 接下来走d3下面的store初始化 -> dispatch(init) -> d4 -> 组合middleware,合并new dispatch -> 返回增强的store
*/
return (createStore) => (...args) => {
// 保存createStore(reducer, initstate) || createStore(reducer), 赋值给store
const store = createStore(...args)
// 定义了一个dispatch, 调用会 throw new Error(dispatching虽然构造middleware但不允许其他middleware应用
let dispatch = () => {
throw new Error(
'Dispatching while constructing your middleware is not allowed. ' +
'Other middleware would not be applied to this dispatch.'
)
}
// 定义middlewareAPI, 中间件中的store eg -> logger(store)
const middlewareAPI = {
getState: store.getState,
// 添加dispatch并包装一个function, 参数为(reducer, [initstate])
// 向下看一看middlewareAPI作为参数被回调回去,不难理解, 告诉dispath不能再middleware插件中构造
dispatch: (...args) => dispatch(...args),
}
// 调用每一个这样形式的middleware = store => next => action =>{},
// 组成一个这样[f(next)=>acticon=>next(action)...]的array,赋值给chain
const chain = middlewares.map((middleware) => middleware(middlewareAPI))
// compose(...chain)会形成一个调用链, next指代下一个函数的注册, 这就是中间件的返回值要是next(action)的原因
// 如果执行到了最后next就是原生的store.dispatch方法
dispatch = compose(...chain)(store.dispatch)
// 返回增强的store
return {
...store,
dispatch,
}
}
}
问题
Q: dispatch 之后,Redux 是如何去处理的?state 中的数据被修改之后,页面如何去收到更新后的数据? A: 首先会利用当前的 reducer、state 以及传入的参数 action 得到新的 state, 然后通过触发监听数组中的函数,让函数中的使用的 store.getState() 再次触发,起到通知数据更新的作用。
Q:为什么一个临时变量 dispatch 被赋值了 2 次? A:首先从第一个变量返回的 throw Error 可以看出这段代码希望在 middleware 数组被构建时, dispatch 不应该被调用,否则抛错。而在 middlewareAPI 的 dispatch 中被调用了一次但没触发这个 throw Error,是因为其实直到给 dispatch 第二次赋值时才真正调用 dispatch(我们之前解读到直到 compose 函数传入了(store.dispatch) 之后才会触发调用),所以这时 middlewareAPI 的 dispatch 并不会触发。
Q:middlewareAPI 的 dispatch 为什么要用匿名函数包裹? A:目的就是如果每个 middleware 对 dispatch 有所改变,middleware 里面的 dispatch 也会相应做出改变(如一问中所说,compose(...chain)(store.dispatch) 触发了 middlewareAPI 的 dispatch 被调用)。
结尾
本文参考了一些文章:
- redux源码解析掘金:juejin.cn/post/684490…
- redux源码解析知乎:zhuanlan.zhihu.com/p/321362823