前面主要介绍了createStore
,combineReducers
,compose
的实现原理,下面,我们看一下 redux
中最有意思的中间件部分applyMiddleware
。 applyMiddleware
代码很简洁,但是含义很广泛。我们来一起看一下:
首先,我们先来重温一下中间件的使用方法:
- 调用中间件 来看一下
createStore
的源码
if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') { enhancer = preloadedState preloadedState = undefined } //enhancer必须是一个高阶函数 if (typeof enhancer !== 'undefined') { if (typeof enhancer !== 'function') { throw new Error('Expected the enhancer to be a function.') } //增强器 return enhancer(createStore)(reducer, preloadedState) }复制代码
enhancer
是中间件,当createStore
的第二个参数是function
且没有第三个参数时,也可以直接在第二个参数设置中间件。那么中间件就有两种设置方式
createStore(reducer,initState,applyMiddleware(ThunkMiddleware));复制代码
或者
createStore(reducer,applyMiddleware(ThunkMiddleware));复制代码
熟悉了用法之后,我们来看一下applyMiddleware
的实现方式:
export default function applyMiddleware(...middlewares) { return (createStore) => (reducer, preloadedState, enhancer) => { // 生成一个store const store = createStore(reducer, preloadedState, enhancer) let dispatch = store.dispatch let chain = [] //简陋版的store,里面包含了`getState`和`dispatch`两个方法。 const middlewareAPI = { getState: store.getState, dispatch: (action) => dispatch(action) } //将middlewareAPI作为参数注入到每个middleware中去,执行middleware, 返回一个新的链。 //中间件函数(store)=>next=>action. // chain [next=>action,...]; //这个 next 其实store.dispatch. 而`action`就是`dispatch`的action chain = middlewares.map(middleware => middleware(middlewareAPI)) //我们假设有三个中间件,fn1,fn2,fn3,那么下面代码等同于 dispatch=fn1(fn2(fn3(store.dispatch))); //可以发现,中间件所组成的dispatch 其实就是一个执行过fn1,fn2,fn3的函数。 //所以,每个中间件在遇到不是自己处理范围之内的action的时候,会使用next(action),将其传递给下一个中间件。 dispatch = compose(...chain)(store.dispatch) return { ...store, dispatch } }}复制代码
再来一段redux-thunk
中间件的源码做为参考,一起来看一下
function createThunkMiddleware(extraArgument) { return ({ dispatch, getState }) => next => action => { if (typeof action === 'function') { return action(dispatch, getState, extraArgument); } return next(action); };}复制代码
我们从中间件的代码开始入手,一点点剖析applyMiddleware
。
在中间件的代码中有这样一段代码:({dispatch,getState})=>next=>action=>{}
; 结合上面applyMiddleware
的源码来看呢,很容易发现({dispatch,getState})
指的就是middlewareAPI
,是一个简陋版的store
,而action
就是我们dispatch
的action
。那么这个next
是什么呢?
在执行chain = middlewares.map(middleware => middleware(middlewareAPI))
的时候,我们将 middlewareAPI
作为参数传进去,对应着中间件代码中的({dispatch,getState})=>
这一步。则执行完的chain
则表示[(next)=>action=>{},...]
。
继续向下看, dispatch = compose(...chain)(store.dispatch)
这时候,可以发现,其实next
参数其实就是原始的store.dispatch
的这个dispatch
。
那么,applyMiddleware
在多中间件的场景下,是如何工作的呢? 我们来看一下dispatch = compose(...chain)(store.dispatch)
。 假设我们有fn1
,fn2
,fn3
三个中间件,那么以上函数就可以拆解为 dispatch=fn1(fn2(fn3(store.dispatch)))
;
我们举个例子来看:
const a = next => () => { console.log('a pre'); next(); //执行b(c(d())); console.log('a after'); } const b = next => () => { console.log('b pre'); next(); // 执行c(d()); console.log('b after'); } const c = next => () => { console.log('c pre'); next(); //执行 d(); console.log('c after'); } const d = () => { console.log('Hello World'); } function compose(...funcs) { return funcs.reduce((a, b) => (...args) => a(b(...args))) } console.log(compose(a, b, c)(d)())复制代码
打开浏览器的console
,可以发现执行结果为
a preb prec preHello Worldc afterb aftera after复制代码
通过compose
函数,将compose(a,b,c)(d)()
合成为a(b(c(d)))()
; 则首先执行a()
,然后再控制台上打印出a pre
,然后执行next()
,此时的next
其实就是b(c(d))
,然后执行b()
,打印出b pre
,随后在执行next()
,这时的next
表示的是c(d)
,然后执行c()
,打印出c pre
,随后在执行next()
,这时的next
表示的是d
,然后执行d()
,打印出helloWorld
. 随后继续执行,打印出c after
,然后是b after
,a after
。
这个例子很好的解释了中间件的执行过程。我们的compose
函数只是将fn1
,fn2
,fn3
组成了fn1(fn2(fn3()))
函数,不会立即运行最里面的函数,只有fn1
执行了next
之后,才会执行fn2
,同样,只有fn2
执行了next
之后,fn3
才会执行。以此类推。假如,我们在某个层级不执行next
了,那么这个链就断了。
所以,我们在使用redux-logger
这个中间件的时候,必须要把它放在middlewares
的最右边,就是因为担心后面的某个中间件万一不执行next
了,整个链就断了,那么即使他在前面执行了next
,也是没有用的。
这也要求我们,在编写中间件的时候,都要执行一次next(action)
,因为只要每个中间件都常规的执行next(action)
,就能保证这个链不断,原始的dispatch
就可以一直分发下去了。