前面主要介绍了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 pre
b pre
c pre
Hello World
c after
b after
a 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
就可以一直分发下去了。