You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
exportdefaultfunctionapplyMiddleware(...middlewares){//....letdispatch=()=>{thrownewError(`Dispatching while constructing your middleware is not allowed. `+`Other middleware would not be applied to this dispatch.`)}constmiddlewareAPI={getState: store.getState,dispatch: (...args)=>dispatch(...args)}constchain=middlewares.map(middleware=>middleware(middlewareAPI))dispatch=compose(...chain)(store.dispatch)//.....return{
...store,
dispatch
}}}
letdispatch=()=>{thrownewError(`Dispatching while constructing your middleware is not allowed. `+`Other middleware would not be applied to this dispatch.`)}constmiddlewareAPI={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思想,实现状态管理,而不依赖其他的第三库呢?:)
The text was updated successfully, but these errors were encountered: