原文: redux的applyMiddleware源码
记得之前第一次看redux
源码的时候是很懵逼的,尤其是看到applyMiddleware
函数的时候,更是懵逼。当然那也是半年前的事情了,前几天把redux
源码看了下,并且实现了个简单的redux
功能。但是没有实现中间件。今天突然又想看看redux
的中间件,就看了起来。
记得半年之前是从函数声明的下一行就开始看不懂了。。。然后前段时间,看了下柯里化函数,加深了高阶函数的印象,所以今天打算把中间件的源码给撸一下。
我们来看看函数声明的下一行,也就是源码第二行开始看:
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
。不过为了证明,我们还是得从源码上来看。
还记得是怎么调用的中间件的吧,大致如下:
const store = createStore(
reducer,
applyMiddleware(...middlewares)
);
可以看到中间件是在createStore
参数里调用的(在参数里运行函数,导致传递给createStore
的是中间件运行后返回的结果,从上面的中间件源码可以知道,返回的就是携带createStore
参数的函数),现在我们可以进createStore
函数里看看他是怎么处理中间件返回的函数的。
redux
的主要实现都是在createStore
里实现的,所以我们主要看createStore
里处理参数的部分:
export default function createStore(reducer, preloadedState, enhancer) {
if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
enhancer = preloadedState
preloadedState = undefined
}
if (typeof enhancer !== 'undefined') {
if (typeof enhancer !== 'function') {
throw new Error('Expected the enhancer to be a function.')
}
return enhancer(createStore)(reducer, preloadedState)
}
if (typeof reducer !== 'function') {
throw new Error('Expected the reducer to be a function.')
}
//other code
}
从我们调用createStore
可以知道第一个参数是reducer
,第二个参数就是中间件运行之后返回的携带createStore
参数的函数。但是在上面的这段源码里,我们发现是preloadedState
来接收这个携带createStore
参数的函数,感觉不是很多,命名的'不好'。先继续往下看,wow, 是一个判断,他会判断preloadedState
是不是一个函数,第三个参数enhancer
是不是未定义;如果preloadedState
是函数,enhancer
是未定义,那么就会把preloadedState
赋值给enhancer
,并且设置preloadedState
是未定义。 这样就没有问题了,在这里,相当于第三个参数enhancer
接收了携带createStore
参数的函数。
然后第二个判断:
if (typeof enhancer !== 'undefined') {
if (typeof enhancer !== 'function') {
throw new Error('Expected the enhancer to be a function.')
}
return enhancer(createStore)(reducer, preloadedState)
}
他会去运行这个enhancer
。这个enhancer
是什么?就是我们说的携带createStore
的函数。
有意思的是,这个enhancer
直接在这里运行了,并且采用了createStore
作为参数(这个createStore
就是函数呀)。 我们再来看看enhancer(createStore)
返回的是啥:
return function (...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
}
}
有意思,返回的是带有多个参数的函数。
上面的代码相当于:
enhancer(createStore) ~= function(...args) => function(reducer, preloadedState)
可以看到,上面的(...args)
就是相当于(reducer, preloadedState)
。
那么我们再来看看上面的function(...args)
, 额, 直接在第一行就再次调用创建store
,这样不会陷入无限循环吗?不会,因为有参数判断,在createStore
的原方法里不会再执行enhancer
; 所以我们可以发现,在有中间件的时候,真正的执行createStore
是在中间件里去执行的,并且携带的参数是reducer, preloadedState
。
所以上面第一行创建了个store
对象,他返回的属性有:
{
dispatch,
subscribe,
getState,
replaceReducer,
[$$observable]: observable
}
然后新建了个指向dispatch
变量的匿名函数,这个函数在调用的时候抛出异常告诉你不可以在构造中间件时调用dispatch
。
Dispatching while constructing your middleware is not allowed. Other middleware would not be applied to this dispatch.
接下来会创建一个middlewareAPI
对象:
const middlewareAPI = {
getState: store.getState, //获取store里的state
dispatch: (...args) => dispatch(...args) // 调用`dispatch`的时候会抛错,如果在组合中间件之前调用,下面会说
}
一开始我以为是在调用的时候就会报错,可是发现这个对象里的dispatch
携带参数,如果只是单纯抛错,完全可以不需要传递参数,然后向下看下去才看到其中的奥妙。
然后就是对中间件集合middlewares
(数组)进行操作:
const chain = middlewares.map(middleware => middleware(middlewareAPI)) //返回了新的集合,对应的每个中间件调用的结果
然后就是组合这些中间件了, 这里对高阶函数不熟的,可以看下柯里化函数和函数组合::
// china是上面返回的中间件的结果
dispatch = compose(...chain)(store.dispatch)
可以看到这个代码,组合了中间件, 使用compose
这个高阶函数来处理的。我们看下这个高阶函数:
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)))
}
上面的代码比较有意思:
- 第一个判断
如果没有中间件作为参数传递,那么直接返回一个函数,接收对应的参数并且返回这个参数。 - 第二个判断
如果如果这个中间件参数只有一个,那么直接返回这个中间件函数 - 最后一步
那就是多个中间件传递进来的时候,他会借用reduce
方法组合(这个放在后面), 会有个...args
参数,就是(store.dispatch
),等下回说到。
可能你对reduce
不是很熟,可以简单的看下他干了什么事:
['1', 2, 3, 'n'].reduce((a, b) => console.log('a is',a , 'b is', b)) // 这样你就会发现这个方法在这里的作用
其实从注释里也可以知道:
* @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))).
把这些中间件都执行到dispatch
.
再回到上面看compose
的返回:
return funcs.reduce((a, b) => (...args) => a(b(...args)))
我们再看看中间件调用compose
的地方:
dispatch = compose(...chain)(store.dispatch)
从这个地方再配合看compose(...chain) => result
的这个result
.
- 第一个判断
返回的是(arg) => arg
就是相当于result(arg) => arg
, 果然,直接返回这个store.dispatch
- 第二个判断
返回的是唯一的一个中间件result
. 然后中间件直接调用store.dispatch
作为参数。 - 最后一个
这个返回的是一个函数,看起来像这样:
(...args) => a(b(...args))
这样就相当于result(args) => a(b(...args))
,这样就保证每个中间件都会用到dispatch
,并且最终返回这个被扩展过的dispatch
.
然后可以看到中间件函数返回了对象:
{
...store,
dispatch
}
这个dispatch
就是被处理过的dispatch
。