简介
中间件可以扩展redux的功能,比如最常见的 redux-thunk,该中间件使得redux支持异步action。
标准的redux中间件是一个这样的函数。
function myMiidleware({ getState,dispatch }) {
return next => action => {
...
const returnValue = next(action)
return returnValue
}
}
源码分析
function applyMiddleware(...middlewares){
return (createStore) =>(reducer,preloadedState) => {
const store = createStore(reducer, preloadedState)
let dispatch = () => {
throw new Error(
'Dispatching while constructing your middleware is not allowed. ' +
'Other middleware would not be applied to this dispatch.'
)
}
const middlewareAPI = {
getState: store.getState,
dispatch: (action, ...args) => dispatch(action, ...args)
}
const chain = middlewares.map(middleware => middleware(middlewareAPI))
dispatch = compose(...chain)(store.dispatch)
return {
...store,
dispatch
}
}
}
applyMiddleware的返回结果是一个函数,该函数就是一个增强器enhancer,它会在createStore中被调用。
来看看最终的函数执行过程。
首先执行createStore得到store对象,接着又定义了dispatch函数和middlewareAPI对象,由middlewareAPI对象就可以知道为什么中间件的参数是{getState,dispatch} 。遍历中间件数组得到中间件链chain。这个chain大概长这样
chain = [
next => action => {
const returnValue = next(action)
return returnValue
},
next => action => {
const returnValue = next(action)
return returnValue
},
next => action => {
const returnValue = next(action)
return returnValue
},
...
]
接下来是最重要的一步,调用compose函数,得到最终的dispatch函数。
dispatch = compose(...chain)(store.dispatch)
理解了compose函数,就明白了为什么中间件可以链式调用。以下为compose源码
function compose(...funcs) {
if (funcs.length === 0) {
// infer the argument type so it is usable in inference down the line
return (arg) => arg
}
if (funcs.length === 1) {
return funcs[0]
}
return funcs.reduce((a, b) => (...args) => a(b(...args)))
}
参数个数为0或1的时候好理解,主要看参数个数大于1的情况。此时compose调用funcs.reduce((a, b) => (...args) => a(b(...args)))并返回。
const composedFunc = compose(middleware1,middleware2,middleware3);
//composedFunc = (...args)=>a(b(...args));
其实composedFunc = compose(middleware1,middleware2,middleware3)就等同于 composedFunc = (...args) => middleware1(middleware2(middleware3(args)))。
下面通过一个例子看看dispatch = compose(...chain)(store.dispatch)发生了什么,以及为什么调用dispatch会链式调用所有中间件。
function compose(...funcs) {
return funcs.reduce((a, b) => (...args) => a(b(...args)))
}
function middleware1(next)
{
console.log('middleware1-next',next);
return action => {
console.log('middleware2 action',action);
return next(action);
}
}
function middleware2(next)
{
console.log('middleware2-next',next);
return action => {
console.log('middleware2 action',action);
return next(action);
}
}
function middleware3(next)
{
console.log('middleware3-next',next);
return action => {
console.log('middleware3 action',action);
return next(action);
}
}
function dispatch(action){
console.log('dispatch')
return action
}
//相当于composedFunc = (...args) => middleware1(middleware2(middleware3(args)))
const composedFunc = compose(middleware1,middleware2,middleware3);
运行这段代码是不会打印任何输出的,因为compose只是根据传入的函数生成一个组合之后的函数。
在这段代码的最后增加const _dispatch = composedFunc(dispatch),再运行,则打印的信息如下
middleware3-next ƒ dispatch(action){
console.log('dispatch')
return action
}
middleware2-next action => {
console.log('middleware3 action',action);
return next(action);
}
middleware1-next action => {
console.log('middleware2 action',action);
return next(action);
}
需要注意的是,只有最后一个中间件的next参数是dispatch函数,其他中间件的next参数是前一个中间件的返回值。正如redux官方文档里说的
The last middleware in the chain will receive the real store's dispatch method as the next parameter, thus ending the chain.
我们得到的_dispatch函数签名是这样的
const _dispatch = action => {
console.log('middleware1 action',action);
return next(action);
}
_dispatch可以看作middleware1的执行的结果,但是_dispatch已经通过next将每个中间件串联了起来,所以当我们调用_dispatch({type:'test'})的时候回看到如下结果
"middleware1 action" Object { type: "test" }
"middleware2 action" Object { type: "test" }
"middleware3 action" Object { type: "test" }
"dispatch"
可以看出,_dispatch会按顺序调用每个中间件,最后调用dispatch。
如果想中断中间件链,只要在某些情况下不调用next即可,比如redux-thunk的做法,当action是函数的时候不调用next。
function createThunkMiddleware(extraArgument) {
return ({ dispatch, getState }) => (next) => (action) => {
if (typeof action === 'function') {
return action(dispatch, getState, extraArgument);
}
return next(action);
};
}
const thunk = createThunkMiddleware();
thunk.withExtraArgument = createThunkMiddleware;
export default thunk;