什么是middleware
middleware翻译过来叫中间件,这是一个很抽象的名词,按我的理解的话:“中间”是个形容词,“件”应该是一个名词。那么我们重点关注中间这个词,中间,是在上面中间呢?其实就是在你执行正常业务的代码中间插入一部分其它代码,具体可以是在正常代码的执行前,也可以在正常代码执行后。其实学过Spring的童鞋应该很好理解,这个东西跟Spring的切面编程很类似。。。
有啥用
前面说了,这个技术可以让我们在正常的业务代码前后执行一部分其它代码,这个其它代码可以包括:日志、鉴权啊等到一些公共处理代码。简单来说,只有你想不到,没有做不到。
怎么用
话不多说,先上代码:
const logger1 =store=> next=> action => {
console.log('dispatching logger', action)
next(action)
console.log('next state logger', action)
}
const logger2 =store=> next=> action => {
console.log('dispatching logger', action)
next(action)
console.log('next state logger', action)
}
const store = createStore(
reducer,
applyMiddleware([logger1,logger2])
)
我们看到上面的代码中,首先声明logger1、logger2两个middleware(没错,这两个看起来很奇怪的变量就是middleware了。。。),然后在创建store的时候通过applyMiddleware来绑定到dispatch上去,这样每次我们分发(dispatch)一个action的时候,两个middleware里的代码都会执行。
对,就是这么简单,表面的简单,背后是大量逻辑很复杂的代码。。。
源码解析
下面先上applyMiddleware的源码:
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
}
}
}
这个函数是redux在createStore函数中调用的,所以它返回一个匿名函数,我们只需要关心内部匿名函数的实现就好了。
让我们来一步步分析短短的几行代码:
变量声明
我们先看applyMiddleware内部匿名函数的前几行代码:
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)
}
第一行代码调用createStore函数创建一个store对象,这个没什么特别的,过。。。
第二行声明一个dispatch变量,指向一个箭头函数,函数直接报错,用来告诉用户我这会儿正初始化呢,你敢dispatch我就报错给你看!
第三行代码声明一个middlewareAPI的对象,里面包含两个属性:getState和dispatch。
getState没什么好说的,重点是这个dispatch属性,这个属性指向一个箭头函数,函数直接执行dispatch函数(这个dispatch函数可不是store原生的dispatch,而是我们在第二行声明的dispatch变量指向的箭头函数。
这块相对来说比较简单,但是为了后面我们好理解,这里来对前面的变量声明做如下优化:
const store = createStore(...args) // 不变
let temDispatch = () => { // 为了跟store.dispatch 区分,我们把变量名称修改为temDispatch;
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,
APIDispatch: (...args) => temDispatch(...args) // 同样的,为了区分,我们这里用APIDispatch来表示属性变量
}
如上代码所示,为了同store.dispatch方法区分,我们分别用 temDispatch和 APIDispatch这两个名称来替代原来的dispatch。
分拆Middleware 函数
接下来我们看下一行代码:
const chain = middlewares.map(middleware => middleware(middlewareAPI))
middlewares我们知道是一个包含中间件的数组,通过数组的map处理后,我们将会"执行一次"中间件函数,然后将返回值放到chain的数组中。
上面我们说"执行一次“中间件函数,其实说法有点不太好理解,接下来我们慢慢分析中间件:
const logger1 =store=> next=> action => {
console.log('dispatching logger', action)
next(action)
console.log('next state logger', action)
}
上面是一个最简单的中间件形式,但是还是有点复杂,我们可以先把这个中间件拆分成以下的样子:
const inner = action => {
console.log('dispatching 333 logger', action)
next(action)
console.log('next state 4444 logger', action)
};
const middle = next => action => {
console.log('dispatching 333 logger', action)
next(action)
console.log('next state 4444 logger', action)
};
const logger1 = store => next => action => {
console.log('dispatching logger', action)
next(action)
console.log('next state logger', action)
}
如上所示,我们的middleware其实是一个箭头函数,不严谨的说,这个函数可以被logger1()()()
这样被调用,因为第一次和第二次被调用都返回一个新的箭头函数,这里为了好理解,我们把他们拆分为middle 和inner函数(一般是不能这么写的,因为内部的箭头函数还要通过闭包获取外部的变量值)。
说了这么多,其实最终可以归结为一句话,那就是我们的chain数组里放的都是middle函数,也就是chain是一个middle函数的集合,这点很重要,我们后面还会说到这个。
链式调用
我们继续看下一行代码:
dispatch = compose(...chain)(store.dispatch)
等价于
temDispatch = compose(...chain)(store.dispatch)
这行代码看着很简短,其实很难理解,我们一步步来看。
转换链式函数
我们首先来看 compose(...chain)这行代码。以下是compose代码的实现:
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)))
, 代入compose(...chain),chain我们上面说到,是一个middle函数的数组,然后经过reduce处理,这里比较麻烦,我们一点点来解释:
(a, b) => (...args) => a(b(...args))
上面就是reduce函数的回调,
(...args) => a(b(...args)
这是回调的返回值,也是一个箭头函数,我们把它命名为reduceMiddleFunc;
a变量为上次回调的返回值(不出意外的话,就是一个箭头函数,要么是chain数组的第一个值,也就是一个middle函数,要么就是上次回调的返回值,就是一个reduceMiddleFunc函数 ),
b变量为当前循环的值,也就是一个middle函数。
这样可能不太好理解,举个例子吧,假如说原来的chain数组的值为[middle1,middle2,middle3,middle4]。那么compose(...chain)之后,我们得到(...args) => middle1(middle2(middle3(middle4(...args))))这样一个箭头函数。我们把它命名为 chainFunc.
执行链式函数
原来的代码是:
dispatch = compose(...chain)(store.dispatch)
等价于
temDispatch = compose(...chain)(store.dispatch)
经过我们上面的分析后,我们得到以下代码:
const chainFunc = (...args) => middle1(middle2(middle3(middle4(...args))));
temDispatch = chainFunc(store.dispatch)
接下来我们来看 chainFunc(store.dispatch),也就是我们要执行这个链式函数了,如下:
const chainFunc = (...args) => middle1(middle2(middle3(middle4(...args))));
temDispatch = chainFunc(store.dispatch)
// 相当于下面一行
temDispatch = middle1(middle2(middle3(middle4(store.dispatch))))
【注意】:此处的store.dispatch是调用createStore创建的元素store的 dispatch方法,后面我们会覆盖原生的dispatch,所以这里需要注意下。
返回store对象
我们来看applyMiddleware的最后一行代码,
return {
...store,
dispatch// 也就是temDispatch
}
这个其实是createStore函数的返回值,也就是说我们上面定义的temDispatch会覆盖掉初始的store中dispatch。
也就是说,当你调用调用store.dispatch(action)的时候,就相当于是调用 middle1(middle2(middle3(middle4(store.dispatch))))(action),只要最内部的store.dispatch才是调用真正的dispatch方法。
我们来简化一下这个代码:
const param = middle2(middle3(middle4(store.dispatch)));
store.dispatch(action)
等价于
middle1(param)(action)
还记得middle函数吗?
const middle = next => action => {
console.log('dispatching 333 logger', action)
next(action)
console.log('next state 4444 logger', action)
};
当我们执行middle1的时候,就会把param当做next参数来执行,然后返回一个 inner函数:
这是inner函数:
const inner = action => {
console.log('dispatching 333 logger', action)
next(action)
console.log('next state 4444 logger', action)
};
那么上面的代码就可以修改为如下:
const param = middle2(middle3(middle4(store.dispatch)));
store.dispatch(action)
等价于
middle1(param)(action)
等价于
inner(action)
那么在inner函数内执行next函数,其实就是执行middle2(middle3(middle4(store.dispatch)))这一套,依次类推,就好比是洋葱一样,一直执行到最内部真正的 store.dispatch方法为止。
one more thing
上面我说到,在最后我们用temDispatch这个函数覆盖了原始的 store.dispatch函数,那如果我们是inner中通过 store.dispatch去调用会发什么情况呢?
我们已经说过,applyMiddleware最终会覆盖原始store上的dispatch方法,改成我们的链式调用函数,如果在inner里调用store.dispatch,其实就相当于重新从链式函数的最外层的开始调用,这就进死循环了。。。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。