Redux
源码解读 - applyMiddleware
篇
applyMiddleware
先把源码贴出来:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 export default function applyMiddleware (...middlewares ) { return createStore => (...args ) => { const store = createStore (...args) 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 : (...args ) => dispatch (...args) } const chain = middlewares.map (middleware => middleware (middlewareAPI)) dispatch = compose (...chain)(store.dispatch ) return { ...store, dispatch } } }
刚开始我以为这函数这么短应该会很简单。
但是到后面和createStore
函数结合的时候我才发现比createStore
难多了。
主要是函数嵌套会有点晕(套,就嗯套)。
先说说这个东西的作用。
有些时候,我们想在每一次的状态变化之前,变化之后处理一些和业务无关的逻辑操作,比如写写日志。
那么我们可能会这样写。
1 2 3 4 5 6 7 8 9 10 const store = function fn ( ) { console .log (store.getState ()); store.dispatch ({type :TYPE }); console .log (store.getState ()); } fn ();
但是这样写有一个问题,就是他耦合了业务函数fn
。
试想下如果我们在100
个业务函数中都这样写,有问题吗?
写当然没有问题,但是如果某一天要求在dispatch
前打印下时间,那就完蛋了,要改100
个地方,而且这100
个函数还分布在不同的文件里。
正所谓,复制黏贴一时爽,需求一变火葬场。
所以applyMiddleware
这个API就是为了解决这样的问题而出现的。
middleware
单词意思为中间件,简单点讲就是可以通过外部代码来增强内部的逻辑。
如果以我们的想法,可能会这么写:
1 2 3 4 5 6 7 8 9 10 11 12 const store = const dispatch = store.dispatch ;store.dispatch = (action ) => { console .log (store.getState ()); const newAction = store.dispatch (action); console .log (store.getState ()); return newAction; } store.dispatch ({type :TYPE });
这就是增强dispatch
方法来实现额外的逻辑,不会耦合在业务里面。
而applyMiddleware
也是通过增强dispatch
来实现额外的逻辑。
不过applyMiddleware
支持多中间件,这个可以从它的参数看出来。
举个使用applyMiddleware
的小例子,就以状态改变前后输出日志为例。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 const reducer = (state = 0 , action ) => { switch (action.type ) { case "add" : return state + 1 ; case "delete" : return state - 1 ; default : return state; } }; const loggerMiddleware = (middlewareAPI ) => { return (dispatch ) => { return (action ) => { console .log ("logger before" ); console .log ("state is " + middlewareAPI.getState ()); let newAction = dispatch (action); console .log ("logger before" ); console .log ("state is " + middlewareAPI.getState ()); return newAction; }; }; }; const store = createStore ( reducer, applyMiddleware (loggerMiddleware) ); store.dispatch ({ type : "add" });
当我们执行上面的代码时,会打印:
当然也可以用两个中间件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 const reducer = (state = 0 , action ) => { switch (action.type ) { case "add" : return state + 1 ; case "delete" : return state - 1 ; default : return state; } }; const loggerMiddleware = (middlewareAPI ) => { return (dispatch ) => { return (action ) => { console .log ("logger before" ); console .log ("state is " + middlewareAPI.getState ()); let newAction = dispatch (action); console .log ("logger before" ); console .log ("state is " + middlewareAPI.getState ()); return newAction; }; }; }; const timeMiddleware = (middlewareAPI ) => { return (dispatch ) => { return (action ) => { console .log ('time' ); let newAction = dispatch (action); return newAction; }; }; }; const store = createStore ( reducer, applyMiddleware (loggerMiddleware, timeMiddleware) ); store.dispatch ({ type : "add" });
运行之后就会出现:
ok,开始分析源码。
applyMiddleware
函数传入中间件的数组,然后直接返回了一个函数。
返回的函数传入了一个createStore
,也就是我们上篇帖子说的函数。
1 2 3 return createStore => (...args ) => { }
源代码这样写可能看着晕,完全可以把它拆开。
1 2 3 4 5 return createStore => { return (...arg ) => { } }
主题的逻辑都在我上面标的位置。
1 2 3 4 5 6 7 const store = createStore (...args)let dispatch = ( ) => { throw new Error ( 'Dispatching while constructing your middleware is not allowed. ' + 'Other middleware would not be applied to this dispatch.' ) }
先是通过传入的createStore
创建函数,和第二层的arg
参数创建了一个store
。
定义了一个初始的dispatch
函数。
1 2 3 4 const middlewareAPI = { getState : store.getState , dispatch : (...args ) => dispatch (...args) }
接下来定义了一个middlewareAPI
,对应我们创建中间件时最外层函数的参数
1 const chain = middlewares.map (middleware => middleware (middlewareAPI))
接着处理中间件数组,把中间件的API传进去,也就是解开了一层函数的包装
1 dispatch = compose (...chain)(store.dispatch )
这里我觉得就是这个函数最难理解的一段了。
逻辑上就是合并(compose
)已经给定API
的中间件数组,提供一个最原始的dispatch
,返回一个包装过的dispatch
。
可以先看一下compose
这个函数。
1 2 3 4 5 6 7 8 9 10 11 export default function compose (...funcs ) { if (funcs.length === 0 ) { return arg => arg } if (funcs.length === 1 ) { return funcs[0 ] } return funcs.reduce ((a, b ) => (...args ) => a (b (...args))) }
这段代码中最重要的就是最后一句funcs.reduce((a, b) => (...args) => a(b(...args)))
使用到reduce
这个API
的时候,我很喜欢举一个例子,就是累加:
1 2 const sum = [1 , 2 , 3 ].reduce ((sum, val ) => sum + val);console .log (sum);
上面这段就会输出数字6
啦。
回到原来的函数,箭头函数如果套太深其实看着确实晕,所以把它拆出来。
1 2 3 4 5 funcs.reduce ((a, b ) => { return (...args ) => { return a (b (...args)) } })
这里我们用三个中间件来表示这一个过程。
PS:这里已经没有最外层的middlewareAPI
为参数的函数了,因为上一句的map
操作已经把这一层解开了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 const m1 = dispatch => { return action => { } }; const m2 = dispatch => { return action => { } }; const m3 = dispatch => { return action => { } };
我们知道reduce
如果没有传入第二个参数作为默认值的话,是会选择数组的第一项作为初始值,然后跳过第一次循环。
所以在第二次循环的时候。
a
就为m1
,b
就为m2
,返回值作为下一次的a
。
第三次的循环的时候。
a
就为:
1 2 3 a = (...args ) => { return m1 (m2 (args)) }
b
为m3
,返回值作为下一次的a
。
那么最后返回的就是第四次的a
。
此时a
就为:
1 2 3 4 5 a = (...args ) => { return ((...args ) => { return m1 (m2 (args)) })(m3 (args)) }
m3(args)
返回了一个新的dispatch
。
然后依次是m2(args)
返回新的dispatch
。
最后是m1(args)
返回新的dispatch
。
最后m1
返回的dispatch
作为了新的store
的dispatch
。
也就是最后的代码:
1 2 3 4 return { ...store, dispatch }
而且可以从这个嵌套看出来,越后面的中间件他的执行距离真正的dispatch
会更近。
也就是我们之前使用了两个中间件loggerMiddleware
和timeMiddleware
。
传入的时候是先loggerMiddleware
后timeMiddleware
。
输出的时候就是先loggerMiddleware
后timeMiddleware
。
即
1 2 3 4 5 loggerMiddleware在dispatch之前的输出 timeMiddleware在dispatch之前的输出 真正的dispatch timeMiddleware在dispatch之后的输出 loggerMiddleware在dispatch之后的输出
最后就是在createStore
中在传入了中间件的情况下直接返回。
1 2 3 4 5 6 7 8 if (typeof enhancer !== 'undefined' ) { if (typeof enhancer !== 'function' ) { throw new Error ('Expected the enhancer to be a function.' ) } return enhancer (createStore)(reducer, preloadedState) }
这里需要注意一个点
就是MiddlewareAPI
中的dispatch
是整个store
的dispatch
。
而构造的中间件函数中的dispatch
参数是指包装了剩下中间件的dispatch
。
一般我们会把这个dispatch
写为next
,这样就更便于理解了。
1 2 3 4 5 6 7 8 9 10 11 12 const loggerMiddleware = (middlewareAPI ) => { return (next ) => { return (action ) => { console .log ("logger before" ); console .log ("state is " + middlewareAPI.getState ()); let newAction = next (action); console .log ("logger before" ); console .log ("state is " + middlewareAPI.getState ()); return newAction; }; }; };