Redux中间件机制

2,400 阅读6分钟

首先在前面一篇文章 Redux 原理解析 中已经知道了reudx的一个核心流程,及原理,没看也不要紧

流程: dispatch(action) = dispatch({type:'', payload:{} }) -> 执行 reducer() -> 修改state。

也就是说我只要使用dispatch就会直接执行reducer修改state, 这完全是正确的,那为什么需要中间件?

中间件的作用: 就是在 源数据目标数据 中间做各种处理,有利于程序的可拓展性,通常情况下,一个中间件就是一个函数,且一个中间件最好只做一件事情。

在redux中,中间件的作用在于, 调用 dispatch 触发 reducer之前做一些其他操作,也就是说,它改变的是执行dispatch到 触发 reducer的流程。

原来流程是这样的:

加入中间件就变成这样了:

理解了redux中间件的作用与基本流程,就可以开始分析redux中间件源码啦

复习函数的执行顺序

有必要先复习一下。(至少我第一眼出错了...)

以下输出顺序是什么?

 function fn1() {
   console.log(1)
 }
 ​
 function fn2() {
   console.log(2)
 }
 fn1(fn2())

正确答案 的是2 1 ,因为 fn2() 的返回结果作为fn1的参数。所以fn2需要先执行完毕

时刻记住此执行顺序,对以下分析很重要!!!

redux中间件

先看下redux的中间件怎么写的,下面分析会说到为什么这么写。

 function ({ dispatch, getState }) {
   return next => action => {
     next(action)
   };
 }
 // 这里转换箭头函数并给它加上名字,方便观察,阅读
 function mid1({ dispatch, getState }) {
   return function fn1(next) {
       return function fn1Rf(action){
         next(action)
     };
   }
 }

例子:

 import {  createStore,  applyMiddleware } from 'redux';
 ​
 function countReducer(state = 0, action) {
     switch (action.type) {
         case 'INCREMENT':
           return state + 1;
         case 'DECREMENT':
           return state - 1;
         default:
           return state;
     }
 }
 // 中间件1
 function mid1({ dispatch, getState }) {
   return function fn1(next) {
     return function fn1Rf(action) {
       return next(action)
     }
   }
 }
 // 中间件2
 function mid2({ dispatch, getState }) {
   return function fn2(next) {
     return function fn2Rf(action) {
        return next(action)
     }
   }
 }
 // 中间件3
 function mid3({ dispatch, getState }) {
   return function fn3(next) {
     return function fn3Rf(action) {
       return next(action)
     }
   }
 }
 ​
 function countReducer(state = 0, action) {
     switch (action.type) {
         case 'INCREMENT':
           return state + 1;
     }
 }
                                                             
 const  store =  createStore(countReducer, applyMiddleware(mid1, mid2, mid3));
 ​
 export default store

createStore:

只关注走中间件的流程,也就是假如preloadedState与enhancer必定传了一个。

如果第二个参数preloadedState为函数,则enhancer = preloadedState,不然则需传递第三个参数,

第二个参数与第三个参数不能同时为函数。

 export default function createStore(
   reducer,
   preloadedState,
   enhancer
 ){
   if (
     (typeof preloadedState === 'function' && typeof enhancer === 'function') ||
     (typeof enhancer === 'function' && typeof arguments[3] === 'function')
   ) {
     throw new Error('........')
     )
   }
 ​
   if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
     enhancer = preloadedState
     preloadedState = undefined
   }
   if (typeof enhancer !== 'undefined') {
     if (typeof enhancer !== 'function') {
       throw new Error('.......')
     }
     return enhancer(createStore)(
       reducer,
       preloadedState
     ) 
       // ...enhancer 为undefined 或者不为function 时的后续处理逻辑, 不是本文关键
   }

返回:

 enehancer(createStore)(educer,preloadedState) 

也就是执行 applyMiddleware()的返回值就是enehancer, 接着看下applyMiddleware 的实现,看看enehancer是什么

applyMiddleware

 import compose from './compose'
 export default function applyMiddleware(...middlewares){
   return (createStore) => (reducer, preloadedState) => {
     const store = createStore(reducer, preloadedState)
     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: (action, ...args) => dispatch(action, ...args)
     }
 ​
     const chain = middlewares.map(middleware => middleware(middlewareAPI))
     dispatch = compose(...chain)(store.dispatch)
 ​
     return {
       ...store,
       dispatch
     }
   }
 }

可以看到applyMiddleware返回值,确实符合enehancer执行方式

 enehancer(createStore)(educer,preloadedState) 
 // 与
 return (createStore) => (reducer, preloadedState) => {}

主要做了以下几件事情:

  1. 先使用createStore创建 store
  2. 再执行 middlewares.map为 每个中间件提供getState和 dispatch 方法,该dispathch指的是 throw new Error的dispathch, 用作中间件中调用dispathch的错误处理**(不要与store.dispatch混淆)**。
  3. 此时 middlewares.map 执行每个中间的第一层函数,例:mid1,每个fn1和fn1Rf函数中就可以使用了getState和dispatch方法了。
    function mid1({ dispatch, getState }) {  
        return function fn1(next) {  
            return function fn1Rf(action){  
                next(action)  
            };  
        }  
    }  
    第一层函数执行完hou, 将middlewares传入compose 中时,其实每个中间件就只有两层了。  
    function fn1(next) {  
        return function fn1Rf(action){  
            next(action)  
        };  
    }
  1. 接着执行compose组合所有中间件。

compose

源码如下:

 export default 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)))
 }

精简一下,并将箭头函数改掉,方便观察:

 export default function compose(...funcs) {
   return funcs.reduce(function(a, b) {
     return function (...args) {
       return a(b(...args))
     }
   })
 }

例子中mid1, mid2, mid3 被 applyMiddleware中执行过后只剩下两层,并这两层都可访问dispatch,与getState方法,如下:

 function fn1(next) {
   return function fn1Rf(action) {
     return next(action)
   }
 }
 ​
 function fn2(next) {
   return function fn2Rf(action) {
     return next(action)
   }
 }
 ​
 function fn3(next) {
   return function fn3Rf(action) {
     return next(action)
   }
 }
 // 对应的就是applyMiddleware 中的
 const composeFn = compose(fn1, fn2, fn3);

步骤如下:

  1. a为 fn1,
    b为fn2,
    返回:(...args) => fn1(fn2(...args)) , 在这将其命名为A
  2. a为 (...args) => fn1(fn2(...args)),
    b为 fn3,
    返回(...args) => ((...args) => fn1(fn2(...args)))(fn3(...args)) , 在这将其命名为 B

第1步返回结果 等价于

 function A(...args) {
   return fn1(fn2(...args))
 }

第二步返回结果等价于

第二步的a为第一步的返回结果A,在第二步中会自执行A

 function B(...args) {
   return (function A(...args) {
             return fn1(fn2(...args))
          })(fn3(...args))
 }

将A自执行,fn3(...args) 作为A的参数,传入fn2()中

化简得到:

 function B(...args) {
    return fn1(fn2(fn3(...args)))
 }
 const composeFn = compose(fn1, fn2, fn3);
 // composeFn === B
 composeFn = function(...args) {
    return fn1(fn2(fn3(...args)))
 }

接下来继续分析执行composeFn 就得到了最终的我们需要的dispatch

 // 模拟, store.disptch,避免与最终的到的dispatch混淆,命名为storeDispatch
 let storeDispatch = (action)=> {
   console.log('修改成功')
 }
 // 执行composeFn                                  
 dispatch = composeFn(storeDispatch);
 // 得到
 dispatch = fn1(fn2(fn3(storeDispatch)))

将fn3,fn2, fn1执行后 整理得到:

 dispatch = function fn1Rf(action) {
   return function fn2Rf(action) {
     return function fn3Rf(action) {
         // 这个 storeDispatch 是传入的 store.disptch
       return storeDispatch(action)
     }
   }
 }

中间件在redux内部执行顺序是跟传入的顺序反过来的。

但最终组合完毕,使用dispatch时执行顺序跟中间件传入的顺序是保持一致的。

因为一个完整的中间件是由三层函数嵌套的!!!

到此为止,中间件核心原理就解释完毕了

编写中间件的注意点

正常情况下一个中间价的基本结构, 但要注意以下三点

 function mid1({ dispatch, getState }) {
     //1.使用 dispatch() 会触发错误
   return function fn1(next) {
       //2. 使用 dispatch() 会触发错误
     return function fn1Rf(action) {
         //3. 使用 dispatch() 会导致死循环
       next(action)
     }
   }
 }

原因:

第一个和第二个原因如下:

applyMiddleware 中 middlewareAPI 中的dispatch 指向的是 带有 throw new Error()的这个 dispatch 函数,所以在组合中间件时使用dispatch 就会抛出异常。

 export default function applyMiddleware(...middlewares){
   return (createStore) => (reducer, preloadedState) => {
     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: (action, ...args) => dispatch(action, ...args)
     }
 ​
     const chain = middlewares.map(middleware => middleware(middlewareAPI))
     dispatch = compose(...chain)(store.dispatch)
   }
 }

第三个原因:

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

这段代码最终组合中间件的结果 (上面已经分析过了):

结果如下:

 // 暂时命名为 mark
 dispatch = function fn1Rf(action) {

     // 此时按照第三个错误原因在这执行 dispatch(), 这个dispatch指向的是mark, 所以会导致,无限重新执行fn1Rf
    // dispatch() , 死循环
   return function fn2Rf(action) {
     return function fn3Rf(action) {
         // 这个storeDispatch是 compose(...chain)(store.dispatch) 是传入的 store.dispatch
         // 避免混淆,使用storeDispatch命名, 源码中就是到dispatch(action)
       return storeDispatch(action)
     }
   }
 }