Redux的action和reducer已经足够复杂了,如今还须要理解Redux的中间件。为何Redux的存在有何意义?为何Redux的中间件有这么多层的函数返回?Redux的中间件到底是如何工做的?本文来给你解惑,Redux中间件从零到“放弃”。git
本文的参考网站只有二个,首当其冲的就是Redux的官方网站,本文的思考过程大多参考官方给出的例子。还有一个就是Redux的经典中间件,能够说Redux的中间件的产生就是为了实现它——redux-thunk。es6
写在前面:本文其实就是我理解Redux中间件的一个思考过程,中间难免来自我我的的吐槽,你们看看乐乐就好。github
咱们为何要用中间件?这个问题提的好!为了回答这个问题,我如今提出一个需求,全部的store.dispatch
都要监控dispatch以前和以后的state
变化。那么咱们会怎作呢?So easy,直接先后都加上console.log(store.getState())
就能够了不是吗?redux
console.log('dispatching', action)
store.dispatch(getTodos({items:[]}))
console.log('next state', store.getState())
console.log('dispatching', action)
store.dispatch(getTodos({items:["aaa"]}))
console.log('next state', store.getState())
复制代码
没错,咱们能够这么作。不过若是夸张点,我有成千上万的dispatch,那么console.log就要dispatch的数量*2了。而后当咱们幸幸苦苦打完点,产品要上线了,咱们须要把断点都关闭。这个时候难道咱们要一个个去注释删除吗?数组
不,我不干,这样可能还会改错。那么咱们将此功能独立出来试试,这样不就能够实现复用了。将公用代码写入一个方法,而后变化的参数提取出来。promise
function dispatchAndLog(store, action) {
console.log('dispatching', action)
store.dispatch(action)
console.log('next state', store.getState())
}
dispatchAndLog(store, getTodos({items:[]}))
dispatchAndLog(store, getTodos({items:["aaa"]}))
复制代码
这样是否是就方便了不少,注释的话只须要注释两行,而不是随着dispatch成倍数增加。可是我以为这样写,对于其余合做的小伙伴不友好,至关于我本身写了一套语法出来。最好仍是使用官方的store.dispatch
的时候,自定义函数一块儿执行了。bash
能够这样改写store.dispatch
,将store.dispatch
赋值给next
,而后将diapatch
变成咱们自定义的函数,在这个自定义的函数中调用next
,也就是原dispatch
。这样就玩美地改写了dispatch
,保留了原始功能,还添加了自定义的方法。app
const next = store.dispatch
store.dispatch = function dispatchAndLog(action) {
console.log('dispatching', action)
let result = next(action)
console.log('next state', store.getState())
return result
}
复制代码
锵锵锵~~~这个时候Redux中间件的雏形就出现了。异步
MiddleWare就是对dispatch方法的一个改造,一个变异。函数
那么假象一下,我不只须要监控state,我可能还有其余的功能。并且与监控state的方法相互独立。也就是我须要多个中间件,那么该如何实现呢?
咱们能够将每次的变异store.dispatch
都传递给一个新的参数,传入下一次变异之中执行,可是要像这样next1
,next2
……这样源源不断地下去吗?
const next = store.dispatch
const next1 = store.dispatch = function dispatchAndLog1(action) {
console.log('dispatching', action)
let result = next(action)
console.log(result,'next state', store.getState())
return result
}
const next2 = store.dispatch = function dispatchAndLog2(action) {
console.log('dispatching1', action)
let result = next1(action)
console.log(result,'next state1', store.getState())
return result
}
...
...
...
复制代码
这样是否是格式有点丑?让咱们想办法解放next
参数。个人想法是这样的,先写一个compose,用来结合这些方法,而后返回一个变异的dispatch
方法。
const _dispatch=store.dispatch;
function compose(){
return function(action){
_dispatch(action)
}
}
store.dispatch=compose(dispatchAndLog1,dispatchAndLog2)
复制代码
在实现compose方法以前咱们先考虑一个问题,如今middlewares的结构是这样的,多层嵌套,一个函数嵌入一个函数,咱们改如何将这个方法从嵌套中解放出来呢?
function A(){
function B(){
function C(){
}
}
}
复制代码
如何能避免面多层的嵌套?经过把函数赋值给一个参数,能够解放嵌套,但这样不太现实,由于咱们须要建立许多的参数。
const CM=function C(){}
const BM=function B(){
CM()
}
const AM=function A(){
BM()
}
复制代码
为了不建立许多没必要要的引用,咱们能够用传递参数的方式来解决这个问题,直接将函数看成参数传入,那么就要注意一个问题,由于咱们要先传入函数,可是不执行各函数,因此每一个函数咱们都要返回一个函数,也就是建立高阶函数,等都准备好了,从最外层的函数开始调用执行。
function C(){
return function(){}
}
function B(CM){
return function(){
CM()
}
}
function A(BM){
return function(){
BM()
}
}
复制代码
这个方法执行的方式就很恶心,是一个函数嵌套后面的一个函数,将C返回的函数传入B,而后将B返回的函数传入A,最后执行()
,逐层执行函数,这样也就没有逃离回调地狱。
let compose=A(B(C()))
compose()
复制代码
Array.reduce
登场这个时候咱们能够考虑下Array.reduce
这个方法,将这些函数都合并起来。首先先建立一个数组,每一个函数传递一个next
的函数,以便于逐层执行函数。
let array=Array(3)
array[0]=function(next){
return function(){
let res= next();
return res
}
}
array[1]=function(next){
return function(){
let res= next();
return res
}
}
array[2]=function(next){
return function(){
let res= next();
return res
}
}
复制代码
reduce只是合并,并非执行,你们注意了,因此咱们须要在每次执行以前加一层返回函数的操做。注意返回的函数须要和自定义函数的格式一致,也就是返回的函数须要传参next
,至关于prevFunction
是以前两个函数的结合,只有按照自定义函数的格式prevFunction
才会有效。否则只有数组第一第二个会执行,由于初始值就是他们俩执行的结果返回。
function dispatch(){
console.log("dispatch")
return "dispatch"
}
function compose(array){
return array.reduce((prevFunction,currentFunction)=>{
return function (next) {
return prevFunction(currentFunction(next))
}
})
}
console.log(compose(array)(dispatch)());
复制代码
这里我定义了一个dispatch
做为个人最初的next参数,传入中间件的集合之中,最早推入栈的函数,是最后执行的,因次咱们的dispatch
会在最后一层函数执行。细心如大家应该发现了。个人每一个自定义函数都返回了上方next
的返回值。其实就是为了将dispatch
的值返回。这样compose
函数执行以后所获得的值就是dispatch
的值。这样咱们就能够获取原版store.dispatch
的值了。顺便科普下原版store.dispatch
返回的值就是传入action
。
根据上述思路,咱们来写下合并中间件的compose
函数,首先将store.dispatch
给_dispatch
备用,而后compose这个高阶函数的第一层参数是中间件,第二层就是初始next函数,也就是原版的store.dispatch
,咱们传入副本_dispatch
就能够了。最后改造store.dispatch
。
const _dispatch=store.dispatch;
function compose(){
let middlewares=Array(arguments.length).join(",").split(",")
middlewares=middlewares.map((i,index)=>{
return arguments[index];
})
return middlewares.reduce((prevFunction,currentFunction)=>{
return function (next) {
return prevFunction(currentFunction(next))
}
})
}
store.dispatch=compose(dispatchAndLog1,dispatchAndLog2)(_dispatch)
复制代码
这样咱们就能够调用多个中间件啦。
createStore
可是,官方的中间件可不是这么些的。我翻译了下官方对于应用中间件函数applyMiddleware()
的一个定义,其实就是对createStore
的一个加强enhance,也就是封装啦。可是有如下几点须要注意下:
createStore
的dispatch(action)
和getState()
方法。store.dispatch(action)
执行时,中间件的链也会执行,也就是绑定的中间件都要执行。createStore
,而不是createStore
返回的对象store
。也就是说在store
建立的时候,中间件已经执行完毕了。applyMiddleware()
要返回一个createStore
,也就是通过改造以后的createStore
那咱们就根据以上的注意点,理解下官方设定的applyMiddleware()
。 首先是如何加强 createStore
,同时有保证原有功能?
applyMiddleware()
要返回一个createStore
,也就是通过改造以后的createStore
function applyMiddlewareTest(){
return (createStore)=>{
return function (reducer) {
return createStore(reducer)
}
}
}
复制代码
这样调用applyMiddlewareTest()(createStore)(reducer)
不就等同于createStore(reducer)
。
store.dispatch(action)
执行时,中间件的链也会执行,也就是绑定的中间件都要执行。由于咱们不会控制中间件的数量applyMiddlewareTest(m1,m2,m3……)
,因此咱们采用arguments
的特性,来获取中间件的数组,处理一下以后,调用咱们已经写好的compose函合并一下,传给_dispatch
,最后利用Object.assign
拷贝store
以及变异的dispatch
。
function applyMiddlewareTest(){
let middlewares=Array(arguments.length).join(",").split(",")
middlewares=middlewares.map((i,index)=>{
return arguments[index];
})
return (createStore)=>{
return function (reducer) {
let store = createStore(reducer)
let _dispatch=compose(middlewares)(store.dispatch)
return Object.assign({},store,{
dispatch:_dispatch
})
}
}
}
复制代码
createStore
的dispatch(action)
和getState()
方法。咱们如今写的中间件是没法从函数内部中获取到dispatch(action)
和getState()
,因此咱们须要多写一层函数,传入dispatch(action)
和getState()
。为了简洁,咱们能够传入一个对象,包含了入dispatch(action)
和getState()
两个方法
function dispatchAndLog2({dispatch,getState}){
return function (next){
return function (action) {
console.log('dispatching1', action)
let result = next1(action)
console.log(result,'next state1', store.getState())
return result
}
}
}
复制代码
这个函数能够简化为es6的写法:
const dispatchAndLog2=({dispatch,getState})=>next=>action{
....
}
复制代码
出现了!三层函数啊,第一层为了传递store的dispatch(action)
和getState()
方法,第二层传递的参数next
是下一个待执行的中间件,第三层是函数本体了,传递的参数action
是为了最终传递给dispatch
而存在的。
回到applyMiddlewareTest
,中间件中须要的dispatch
和getState
,咱们能够加几行代码实现。直接执行中间件的第一层,将两个方法传递进去。此处须要注意dispatch
由于咱们须要传递的dispatch
是变异以后的,而不是原生的。因此边咱们改写下dispatch
的方法,让中间件调用此方法时,是变异后的dispatch
。否则中间件中执行的dispatch就没法执行中间件了。
function applyMiddlewareTest(){
...
let _dispatch=store.dispatch
let _getState=store.getState
let chain = middlewares.map(function (middleware) {
return middleware({
dispatch:function dispatch() {
return _dispatch.apply(undefined, arguments);
},
getState:_getState
});
});
_dispatch=compose(chain)(store.dispatch)
....
}
复制代码
最后测试一波本身写的中间件是否成功:
function logger({ getState }) {
return function(next){
return function(action){
console.log('will dispatch', action)
const returnValue = next(action)
console.log('state after dispatch', getState())
return returnValue
}
}
}
const ifActionIsFunction = {dispatch,getState} => next => action => {
if (typeof action === 'function') {//若是是函数就执行并返回,而后再函数中执行dispatch,至关于延迟了dispatch。
return action(dispatch, getState);
}else{
let res=next(action)
return res
}
}
let store=applyMiddlewareTest(logger,ifActionIsFunction)(createStore)(rootReducer)
store.dispatch((dispatch,getState)=>{
return new Promise((resolve,reject)=>{
setTimeout(()=>{
dispatch(getTodos({items:["aaaa"]}))
console.log(getState())
resolve()
},1000);
})
})
复制代码
运行是成功的,这里我写的中间件的功能是是若是action
是函数,那么就返回函数的执行结果,而且向函数中传入dispatch
和getState
方法。这样就能够在action
函数中调用dispatch了。机智如你必定发现了这个就是异步的一个实现,也就是redux-thunk
的基本逻辑。(其实就是参照redux-thunk
写的。)
这里还有一个隐藏功能不知道你们发现了没有,我返回的是一个promise,也就是说我能够实现then
的链式调用。
store.dispatch((dispatch,getState)=>{
return new Promise((resolve,reject)=>{
setTimeout(()=>{
dispatch(getTodos({items:["aaaa"]}))
console.log(getState())
resolve("then方法调用成功了吗?")
},1000);
})
}).then((data)=>{
console.log(data)
})
复制代码