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)
}
首先我们来看看中间件的定义:action发起之后的数据流是这样的--action -> reducer -> store,而加上中间件的处理之后,数据流就变成 action -> middlewareA -> middlewareB ->... -> reducer -> store,相当于在抵达Reucer之前 action 可以进行扩展,也就是说我们可以控制每一个流过的action,选择一些我们期待做出修改的action进行响应操作。常用的中间件有异步支持(redux-thunk)、打印日志(redux-logger)等。
compose 方法
在解读applyMiddleware之前,先看看Redux另一个API,也是applyMiddleware里面使用到的方法,compose,代码很简短,但看起来很复杂:
方法接收一个函数数组,当数组长度大于1时,才开始化合作用。最难理解的部分是方法的返回,Array.reduce累加器方法(可参考MDN文档说明)函数的第一个参数是callback回调,表示数组中的每个元素(从左到右)都应用这个函数,compose中累加器的callback回调展开来看也就是:
其中 a 是上一次执行完callback函数的返回值,b是数组当前元素处理的值。
遍历完函数数组的所有元素后,会返回一个强力函数,第一个数组元素包裹在最外层,最后一个数组元素包裹在最里层,这个函数接受任意的参数 ...args,并从数组后面开始向前,逐个执行,执行完之后的返回值作为下一个函数的参数层层传递。
看起来有点绕,看一个例子就明白了:
可能我们现在还不知道compose到底有什么用,但没关系,稍后就能看到compose的魔法。
applyMiddleware 预览
方法签名
首先来看applyMiddleware的方法签名:
参数
方法返回
applyMiddleware方法返回一个函数,形如:
applyMiddleware 解读
通常,我们会这样使用 applyMiddleware 方法:
还记得在方法createStore()中,一旦遇到如上的调用形式,就会直接返回如下形式:
对这行代码进行拆分,并进行分析:
也就是说,creatStore方法中一旦遇到了应用中间件参数的时候,会依次传入 createStore(自身),reducer和reloadedState,层层执行,最终返回一个对象,而这个对象具体的内容则由applyMiddleware方法具体定义。 那么creatStore遇到中间件的情况到底返回了什么,我们接着看看applyMiddleware的详细代码,直接看到内部返回对象的那一个函数中:
我们看到,函数用传入的参数去初始化了一个Store,接着,经过一系列处理之后,返回了这个store,并用一个dispatch覆盖了原来store中的dispatch方法。到这里我们可以发现,creatStore遇到中间件的情况的时候,返回值和createStore原有返回的Store对象相同,提供相同的方法,不同的是dispatch被覆盖了,或者说经过处理之后被增强了。
再接着看函数发生了什么:
这里声明了middlewareAPI 常量,作用就是Redux暴露给中间件的接口,一个getState()函数,来自原生Store,和一个dispatch方法,注意这个方法并非Store原生,稍候会解释为什么要这样处理。
再看下一部分之前,先介绍一个用于异步处理的Redux中间件,redux-thunk,代码十分简单,寥寥几行,实际上,为了同Redux兼容,中间件函数的设计标准都大同小异:
接着回到 applyMiddleware 的代码中,下一行:
结合上面的redux-thunk,这段代码就不难理解,遍历所有传入的中间件函数,传入middlewareAPI执行,将返回值放到chain数组,这段代码执行完后,chain中的中间件函数大概是这样子的:
接着应用了compose,这一步是最难理解,同时也是最灵巧的一部分:
如果我们有a,b,c三个中间件,那么第一步代码执行完成之后,final是一个这样的函数:
而且a,b,c 三个函数的定义都是统一的形如:
再看第二步,将Store原生的 dispatch 方法作为参数传入 final 函数 ,得到的结果赋值给 dispatch 变量:
当过程结束后,变量dispatch就会得到 a 返回的一个这样的函数:
可能到这里我们还是不能明白它是如何执行多个中间件逻辑的,我们来写一个例子:
还记得我们之前说过的 原生Store.dispatch的返回值是什么吗?不记得可以重新看一下createStore的代码,dispatch的返回值就是action,增强的dispatch方法也同样层层返回了action,即便应用了中间件,入参和返回都是相同的,只是过程不同。
现在,我们就能明白中间件的作用和如何生效的了。中间件更像是一条链子,传入一个action后,每一个中间件都可以对action进行处理,经过一个个中间件处理后的action,最终使用了原生的Store.dispatch()来发起,应用中间件的修改。注意,中间件的next(action)是必须的,如果不调用next,就会使得中间件执行链断开,导致最终不能发起action。
redux-thunk
说明一下,异步操作的时候,我们期待发起多个action,例如,获取数据之前发起一个action,正在获取数据也可以发起一个action,获取到数据的时候再发起一个action。接下来我们来看看redux-thunk是如何实现支持异步操作的,在此之前,我们先看看一个应用了redux-thunk的actionCreator的使用例子:
我们多次强调,actionCreator返回的必须是action对象,但是这个actionCreator返回的是函数,而且其参数是 dispatch,然后我们在逻辑里发起一个异步请求数据的操作,用 dispatch 发起了两个action,从而支持异步发起多个action的需求,具体为什么返回的是一个参数为dispatch的函数,还要看看redux-thunk的具体实现:
原来,redux-thunk对返回的action进行了判断,如果是function类型,也就代表着需要处理异步逻辑,此时传入redux提供的middlewareAPI,即getState方法和dispatch方法,执行action函数。注意这里的返回值不再是next,这里返回的是actionCreator执行完后的返回值(一般为undefined),所以会中断中间件链的执行,原因很简单,action为函数的时候并非真正发起了action而是为能在action中使用dispatch方法多次发起action。
就是那么简单。。。
middlewareAPI中的dispatch
这里说到了redux提供的middlewareAPI,不知道还记不记得之前说到过,dispatch 有点奇怪:
看到dispatch变量,其实是作为一个闭包变量返回的。变量有两个赋值的时候,第一次赋值是定义了一个函数,直接抛出了一个错误,第二次则是通过 compose(...chain)(store.disaptch ) 赋值,也就是我们熟悉的增强过后的 dispatch 赋值,那什么要多此一举初始化了一个错误的输出函数呢?查看了redux的issuse后我得到了答案:
但是这样做就会出现一个问题,例如有个中间件是这样的:
这个中间件发起的action不会经过中间件调用链传递,而是直接使用store.dispatch()方法发起,原因很简单:
为了避免action不经过中间件传递的错误,redux修改了dispatch,也就是我们现在看到的样子:
这样子,在中间件调用链未初始化完成之前,调用dispatch就会报错误提示。
redux-logger
最后我们再来看一个常用的中间件,redux-logger,其作用是输出action发起前和发起后,state树的值。
redux-logger 的任务是输出action发起前和发起后,state树的值。它不关心action经过中间件链条时,发生了什么变化,它只关注结果,这也是为什么它的顺序要放在最后面的原因,例如,我们在实际开发中常常这样使用中间件:
action在传递中间件的过程中,直到最后一个中间件(上文也就是redux_logger)时,才会应用原生的dispatch方法发起,此时才能看到state在action前后的变化,这也就是redux-logger为什么要放在后面的原因了。
最后
Redux源码解读就到此结束了,总体来说,整个框架的设计非常的简洁易读,不禁感叹作者之强了,将flux思想转换为redux思想,最终写出一个易用的框架。在实际开发中,通常不直接使用redux作为react的扩展,而是会使用react-redux这个库,正如redux所言,redux并非为react而生,react-redux充当了兼容二者的关系,但其本源是通过react的context,提供一个全局的provider组件包裹整个app应用,将redux定义在provider上,再通过封装context获取数据,更新数据的逻辑来使得在react任一个组件中都能非常简洁地使用redux。
其实我们也可以看到,react-redux实际上是基于react的context设计的,是否有这样一天,react的context会变得更为强大简洁,使得在react就可以应用redux思想,实现状态管理,而不依赖其他的第三库呢?:)