配色: 字号:
redux middleware 的理解
2016-12-05 | 阅:  转:  |  分享 
  
reduxmiddleware的理解



前言



这几天看了reduxmiddleware的运用与实现原理,写了一个百度搜索的demo,实现了类似redux-thunk和redux-logger中间件的功能。







redux中间件是通过函数式编程实现,因此要阅读源码需要有一定函数式编程基础,比如柯里化函数的实现,否则难以理解源码的缘由。接下去通过这个demo给大家讲解个人对中间件的理解,如有问题,comeon指正。



reduxmiddleware是什么



如果没有中间件的运用,redux的工作流程是这样action->reducer,这是相当于同步操作,由dispatch触发action后,直接去reducer执行相应的动作。但是在某些比较复杂的业务逻辑中,这种同步的实现方式并不能很好的解决我们的问题。比如我们有一个这样的需求,点击按钮->获取服务器数据->渲染视图,因为获取服务器数据是需要异步实现,所以这时候我就需要引入中间件改变redux同步执行的流程,形成异步流程来实现我们所要的逻辑,有了中间件,redux的工作流程就变成这样action->middlewares->reducer,点击按钮就相当于dispatch触发action,接下去获取服务器数据middlewares的执行,当middlewares成功获取到服务器就去触发reducer对应的动作,更新需要渲染视图的数据。中间件的机制可以让我们改变数据流,实现如异步action,action过滤,日志输出,异常报告等功能。



如何自定义中间件



redux提供了一个叫applyMiddleware的方法,可以应用多个中间件,这样就可以当触发action时候就会被这些中间件给捕获,这边我们分别定义了搜索和日志的中间件,而且中间件传入顺序是先搜索中间件再日志打印中间件,这个顺序是有讲究的,下文会说明的



复制代码

import{createStore}from''redux''

importapplyMiddlewarefrom''./applyMiddleware/applyMiddleware''

importcomposefrom''./applyMiddleware/compose''

importreducerfrom''./reducers''

importloggerMiddlewarefrom''./middlewares/loggerMiddleware''

importsearchMiddlewarefrom''./middlewares/searchMiddleware''



constcreateStoreWithMiddleware=compose(

applyMiddleware(

searchMiddleware,

loggerMiddleware

),

window.devToolsExtension?window.devToolsExtension():f=>f

)(createStore)

复制代码

searchMiddleware是搜索中间件,其实它就是仿照redux-thunk的实现



复制代码

exportdefaultfunctionthunkMiddleware({dispatch,getState}){

returnnext=>action=>{

/如果action是一个函数,则先执行action,否则通过next进入到下一个action对应的reducer/

typeofaction===''function''?

action(dispatch,getState)://这里action(dispatch,getState)指的是getThenShow返回函数的执行

next(action);

}}

复制代码

我们先来分析下中间件的执行顺序,下文再来解释为什么这里结构是()=>next=>action=>{}以及源码是如何实现中间件链。这里的代码非常简单,就是一个三目运算符,当action是一个函数类型,就直接执行这个action函数,否则就通过next来链接到下一个组件,next是专门用来串联组件间的执行顺序。我们知道,如果没有中间件的处理,action只能返回一个对象格式,否则reducer不能进行处理action传过来的行为,但是有了中间件,我们就可以“肆意妄为”,只要保证最终传给reducer的action是一个对象,期间从触发action到真正到达reducer我想要action是什么就可以是什么,下面是搜索逻辑所对应的action



复制代码

exportfunctiongetThenShow(value){

returndispatch=>{

consturl=''https://gsp0.baidu.com/5a1Fazu8AA54nxGko9WTAnF6hhy/su'';

jsonp(url,{wd:value},''cb'',(data)=>{

dispatch({

type:SHOW_MESSAGE_SUCESS,

lists:data.s

});

})

}

}

复制代码

当执行dispatch(getThenShow())时,上文所说的searchMiddleware就会捕获到这个action,因为第一次传过来的action是



复制代码

functon(dispatch){

consturl=''https://gsp0.baidu.com/5a1Fazu8AA54nxGko9WTAnF6hhy/su'';

jsonp(url,{wd:value},''cb'',(data)=>{

dispatch({

type:SHOW_MESSAGE_SUCESS,

lists:data.s

});

})

复制代码

如上所示,因为第一次传过来的action是function类型的,所以searchMiddleware里就会先执行这个action函数,这个aciton执行jsonp方法回调另一个dispatch,从上面代码可以看出这个时候dispatch分发的action是一个对象,type为SHOW_MESSAGE_SUCCESS,lists为搜索结果,因此下一次searchMiddleware捕获到的action为一个对象,从而去执行next(action),也就是去执行下一个中间件loggerMiddleware的内容



复制代码

exportdefaultfunctioncreateLogger({getState}){

return(next)=>(action)=>{

constprevState=getState();

constreturnValue=next(action);

constnextState=getState();

console.log(`%cprevstate`,`color:#9E9E9E`,prevState);

console.log(`%caction`,`color:#03A9F4`,action);

console.log(`%cnextstate`,`color:#4CAF50`,nextState);

console.log(''==============='')

returnreturnValue;

};

}

复制代码

loggerMiddleware里先通过getState()获取到当前状态,接着用通过next(action)执行下一个中间件,由于applyMiddleware传入中间件的顺序是先searchMiddleware再loggerMiddleware,而且loggerMiddleware之后没有其它中间件传入,因此此时的next指的是原生的dispatch,进而会去触发reducer所对应的动作,所以再次调用getSatate()会返回下一个状态内容。



所以当在搜索框输入内容redux执行的步骤为:



1.输入内容触发dispatch(getThenShow())发起第一个action(这里getThenShow()返回的action的是一个函数)



2.searchMiddleware捕获到第一个action,因为action返回是一个函数,所以执行action



3.第一个action执行完后回调再次触发dispatch({type:SHOW_MESSAGE_SUCCESS,lists:...})发起第二个action



4.searchMiddleware捕获到第二个action,执行next(action),链接到loggerMiddleware



5.loggerMiddleware获取当前(prev)状态后执行next(action)从而触发reducer的对应的动作



6.loggerMiddleware再次获取当前(next)状态,然后打印出状态,执行完loggerMiddleware程序



7.回溯到searchMiddleware,searchMiddleware程序执行完



8.整个redux中间件执行完



到这里,我们就把这个demo的中间件执行顺序分析完,那么为什么自定义中间件是()=>next=>action=>{}这样的结构?next又是如何将中间件串联起来的?其实这个两个问题是同一个问题,因为中间件设定这样()=>next=>action=>{}的结构目的就是为了把中间件串联起来的。为了一探究竟,我们还是得来来看看applyMiddleware源码的实现



applyMiddleware源码分析



复制代码

exportdefaultfunctionapplyMiddleware(...middlewares){

return(createStore)=>(reducer,preloadedState,enhancer)=>{

varstore=createStore(reducer,preloadedState,enhancer)

vardispwww.baiyuewang.netatch=store.dispatch

varchain=[]



varmiddlewareAPI={

getState:store.getState,

dispatch:(action)=>dispatch(action)

}

chain=middlewares.map(middleware=>middleware(middlewareAPI))

dispatch=compose(...chain)(store.dispatch)

return{

...store,

dispatch

}

}

}

复制代码

从applyMiddleware执行完后最终返回的也是一个闭包函数,将创建store的步骤放在这个闭包内执行,这样中间件就可以共享store对象。applyMiddleware是这样来对传进来中间件进行函数式编程处理的



1.通过...middlewares将所有的中间件存入到middlewares数组中



2.middlewares数组通过map方法执行生成新的middlewares数组且每一项都传入middlewareAPI,传入middlewareAPI的目的就使得每个中间件都可以访问到store,这时候middlewares数组的每一项都变为了



function(next){

returnfunction(action){...}

}

3.compose方法将新的middlewares和store.dispatch结合起来,生成一个新的dispatch方法。这里的关键点是compose,我们来看看compose的设计



复制代码

exportdefaultfunctioncompose(...funcs){

if(funcs.length===0){

returnarg=>arg

}



if(funcs.length===1){

returnfuncs[0]

}



constlast=funcs[funcs.length-1]

constrest=funcs.slice(0,-1)

constfn=(...args)=>rest.reduceRight((composed,f)=>f(composed),last(...args))

returnfn

}

复制代码

可以看到compose方法实际上就是利用Array.prototype.reduceRight来进行下一步处理的。如果对reduceRight方法不了解的童鞋,可以先看看这里。我们这里可以来模拟一下compose函数处理完的结果,假设我们这边有两个中间件A和B,则传入到compose的func为[A,B],且A、B的形式已经是(next)=>(action)=>{}



复制代码

functionA(next){

console.log(''A...next==='',next)

returnfunction(action){

console.log(''A...action'')

next(action)

}

}

functionB(next){

console.log(''B...next==='',next)

returnfunction(action){

console.log(''B...action'')

next(action)

}

}



functioncompose(funcs){

if(funcs.length===0){

returnarg=>arg

}



if(funcs.length===1){

returnfuncs[0]

}

constlast=funcs[funcs.length-1]

constrest=funcs.slice(0,-1)

constfn=(args)=>rest.reduceRight((composed,f)=>f(composed),last(args))

returnfn

}



varfnArr=[A,B]

vardispatch=compose(fnArr)("store.dispatch")

console.log(''newdispatch==='',dispatch)

复制代码

执行的结果是







由结果可以看到中间件A的next是指向中间件B的最内层闭包函数,而中间件B的next则是指向原生的dispatch,所以通过compose执行完后,所有的中间件就通过next串联起来了。这也就是为什么我们所分析这个百度搜索demo中的searchMiddleware的next是指向loggerMiddleware,而loggerMiddleware的next指向原生dispatch的原因。



4.返回的store新增了一个dispatch方法,这个新的dispatch方法是改装过的dispatch,由上例中这个改装过的dispatch就是指的是中间件A最里层的闭包函数,这也就是为什么说有了中间件就可以捕获action的行为的原理。



到此applyMiddleware源码分析完毕,我们也可以明白为什么自定义组件需要设计成()=>next=>action=>{}的形式,其实也就是设计成柯里化方式,因为这样方便进行compose,从而达到动态产生next方法以及保持store的一致性。

献花(0)
+1
(本文系thedust79首藏)