Redux源码分析(3) - applyMiddleware

1,405 阅读7分钟

1、applyMiddleware 介绍

   applyMiddleware 作为 Redux 的核心 api 之一,本质就是在 dispatch 更改 reducer 之前做一些操作,具体的实现其实就对 store 的增强,其中最终是对 store 中的 dispatch 的增强。关于 applyMiddleware 的使用可参考上一章节 Redux源码分析(1) - Redux介绍及使用 和 官方文档 applyMiddleware

Middleware 可以让你包装 store 的 dispatch 方法来达到你想要的目的。同时, middleware 还拥有“可组合”这一关键特性。多个 middleware 可以被组合到一起使用,形成 middleware 链。其中,每个 middleware 都不需要关心链中它前后的 middleware 的任何信息。

  applyMiddleware 的源码结构图如下图所示。

image

  首先看下 applyMiddleware 的使用方式,以当前 demo 为实例:

// middleware
const Logger = (store) => (next) => (action) => {
    console.info('logger start');
    let result = next(action);
    console.info('logger end');
};
const Test = (store) => (next) => (action) => {
    console.info('test start');
    let result = next(action);
    console.info('test end');
};

let store = createStore(rootReducer, applyMiddleware(Logger, Test));

   这里涉及到两个 api : createStore 、 applyMiddleware 。 在 createStore 的源码分析中,有提到存在 enhancer 时,直接执行下面的语句。

return enhancer(createStore)(reducer, preloadedState);  

   配合当前实例可知。此时的 enhancer 就是 applyMiddleware(Logger, Test) 的结果。

2、applyMiddleware 源码分析

   applyMiddleware 的源码如下:


import compose from './compose';

export default function applyMiddleware(...middlewares) {
    /*
    1、中间件的使用方式如下:let store = createStore(rootReducer, applyMiddleware(Logger, Test));
    2、在 createStore 方法中,当存在 enhancer 时,执行语句  enhancer(createStore)(reducer, preloadedState);
    3、可知  enhancer 即为 applyMiddleware(Logger, Test) 的执行结果,也即下边返回的高阶函数。
    */
    return createStore => (...args) => {
        /*
        1、此处即执行 enhancer 的执行。可知 createStore 参数即为 redux 的 createStore方法, ...agrs 表示 reducer, preloadedState
        2、store 即为createStore方法在没有enhancer的时候执行的结果。也即中间件作用前的原始store
        */
        const store = createStore(...args);
        // 定义了一个dispatch, 调用会 throw new Error(dispatching虽然构造middleware但不允许其他middleware应用 )
        let dispatch = () => {
            //……
        };
        // debugger
        // 定义middlewareAPI, 传递 getState、 dispatch到中间件。这也是中间件中能访问state的原因。
        const middlewareAPI = {
            getState: store.getState,
           // 重新定义dispatch,此处不是直接赋值,是因为 dispatch 是引用赋值,直接赋值的话,后边更改dispatch会影响到原有的dispatch
            dispatch: (...args) => dispatch(...args)
        };
        /*
        1、中间件形式 store => next => action =>{},
        2、执行后即返回 [  (next)=>acticon=>{...next(action)...}]的array,赋值给chain
        3、以当前demo为实例。及此时的Logger执行后的结果为f1, 记 Test执行后的结果为f2
        */
        const chain = middlewares.map(middleware => middleware(middlewareAPI));
   
        /*
        1、compose(...funcs)执行后返回 : (...args)=>(funcA(funcB(...args)),其中funcA、func 为 funcs的元素
        2、以当前实例说明。则 compose(...chain) 执行的结果 (...args)=>(f1(f2(...args)) ,其中 f1、f2为上边定义的 Logger、Test执行的结果
        3、compose(...chain)(store.dispatch) 执行的结果即为 f1(f2(store.dispatch)) ,此时 f2(store.dispatch) 作为了 f1 的next参数
        4、由f1的形式可知。此时 dispatch = (action) => {...  f2(store.dispatch) (action) ...}, 也即store 中的增强的dispatch
        5、当执行 store.dispatch(action)时,实际执行的是 (action) => {...  f2(store.dispatch) (action) ...}。
        6、则此时 f2 会执行,其中 next参数为store.dispatch,action参数为action。
        7、如果有多个中间件。则可知增强的dispatch为:f1(f2(f3(...fn(store.dispatch))))。
        8、则f1执行时 next =  f2(f3(...fn(store.dispatch))),next(action)即执行 f2(f3(...fn(store.dispatch)))(action)
        9、此时即f2执行,此时next = f3(...fn(store.dispatch)),依次类推到最后next为 store.dispatch。
        10、由此也可知,中间件的函数中必须有next(action)语句的执行。
        */
        dispatch = compose(...chain)(store.dispatch)

        return {
            ...store,
            dispatch  // 返回增强的store
        };
    };
}

   applyMiddleware 的源码分析,注释部分还算详细。applyMiddleware 作用中间件之后返回一个高阶函数,由源码可知这个高阶函数形式如下:

const enhancer = createStore => (...args) => { ....... }  //语句(1)

  enhancer 作为参数传入 createStore。当 createStore 方法中存在 enhancer 时会直接执行下边语句:

return enhancer(createStore)(reducer, preloadedState);  // 语句(2)

   此时实际执行的高阶函数就语句(1)的执行,其中传入的参数依次是 createStore (redux 的 createStore 方法)和 reducer、 preloadedState。

   applyMiddleware逻辑都在返回的高阶函数中, 主要干了以下几件事:

2.1、 获取原始store

 const store = createStore(...args);

   由上面的分析可知,createStore 即为 redux 的 createStore 方法, ...args 是 reducer 、preloadedState。所以这句话可以理解:在没有中间件时,通过当前 reducer 创建的 store。在之前的章节中有提到过,中间件本质就是对 store 的增强 ,此处先拿到原始的 store 正是为了后边的增强做准备的。

2.2、中间件初始化

   这里叫中间件初始化,也不是很准确,可以理解为中间件的执行,因为中间件是一个高阶函数,此处定义为中间件的第一次执行,可与上文中的源码结构图对照。其对应的代码如下:

const middlewareAPI = {
    getState: store.getState,
    dispatch: (...args) => dispatch(...args)
};

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

   middlewareAPI 中包含 getState 和 dispatch 属性, middlewares 为传入的中间件列表,通过map执行后,返回的是形如:(next)=>acticon=>{...next(action)...} 的一个数组。以当前demo为实例,分别执行 Logger、 Test,返回结果分别标记为 f1、 f2 (为了方便阐述)

 let f1 = next => action =>{
     // Logger
     next.action()
 }
 
 let f2 = next => action =>{
     // Test
     next.action()
 }
 
 chain = [f1, f2]

2.3、中间件组合

export default function compose(...funcs) {
  // ..... 边界的判断
    const a = funcs.reduce((a, b) => {
        return (...args) => a(b(...args));
    });
    return a;
}

   先来看下 compose 方法的源码。通过数组的 reduce 方法循环执行,实现函数的组合,最终返回一个复合函数。

compose(func1, func2, func3, ... ,funcn) 返回 (...args) => f1(f2(f3(....fn(args))))

2.4、 增强dispatch,并返回最新的store

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

   这句话是整个 applyMiddleware 代码的关键。根据2.3的分析可知 compose(...chain) 执行的结果就是所有中间件函数第一执行(参数为store)后的组合。

有上文可知: f1、f2 ...... fn 都是中间件第一执行之后返回的高阶函数 :(next) => acticon => { ...next(action)...}

compose(...chain)(store.dispatch) 执行的结果可以理解为 f1(f2(f3(....fn(store.dispatch)))的执行,此时 f1 的入参 next  =  f2(f3(....fn(store.dispatch)) ,执行后赋值 dispatch

dispatch = action => {
    //...... before
    f2(f3(....fn(store.dispatch))(action);
    //...... after
}


   组合后将 store.dispatch 作为组合函数的入参,并返回一个新的 dispatch 函数,覆盖 store 中原有的 dispatch 属性,这既保持了与 createStore 输出结果的一致性;也实现了 dispatch 的增强,到这里也解释 applyMiddleware 本质上是对 dispatch 的增强。

   当执行 store.dispatch(action)时,此时的 dispatch 就是被增强的 dispatch,继续分析


根据上边分析,此时的dispatch为:

dispatch = action => {
    //...... before                     // (第1个中间件的before)
    f2(f3(....fn(store.dispatch)(action);         
    //...... after                      // (第1个中间件的after)
}

执行 store.dispatch(action)后,

(1)首先执行:(第1个中间件的before),

(2)继而执行 f2(f3(....fn(store.dispatch))(action),此时 f2 函数的 next = f3(....fn(store.dispatch)。

   f2 = next => action =>{
    //...... before                      // (第2个中间件的before)
     next(action)
    //...... after                       // (第2个中间件的after)
  }
  
(3)执行:  (第2个中间件的before)
 
(4)继而执行 next(action),也即执行  f3(....fn(store.dispatch)(action)

 (5) 以此类推:f1 ---- next = f2(f3(... fn(store.dispatch)))
              
               f2 ---- next = f3(... fn(store.dispatch))

               f3 ---- next = f4(... fn(store.dispatch))
               
               fn ---- next = store.dispatch
               
    ......
    
 (6) 执行:(第2个中间件的after)
 
 (7) 执行:(第1个中间件的after)

   以上部分的分析,也符合 Redux 官文文档中,关于中间件的介绍。每个中间件中执行 next (action) 是保证中间件能够形成 middleware 链 的关键。

...middlewares (arguments): 遵循 Redux middleware API 的函数。每个 middleware 接受 Store 的 dispatch 和 getState 函数作为命名参数,并返回一个函数。该函数会被传入 被称为 next 的下一个 middleware 的 dispatch 方法,并返回一个接收 action 的新函数,这个函数可以直接调用 next(action),或者在其他需要的时刻调用,甚至根本不去调用它。调用链中最后一个 middleware 会接受真实的 store 的 dispatch 方法作为 next 参数,并借此结束调用链。所以,middleware 的函数签名是 ({ getState, dispatch }) => next => action

2.5、 洋葱模型

   通过上文的分析可知,中间件是在 dispatch 更改 reducer 之前做一些动作,根据 2.4 的可知,增强的 dispatch 在执行过程中,实际是:

image

   其执行过程对应的就是洋葱模型,先从外到里层层深入直到最里层,然后从最里层层往外,直到最外层。

   结合本文中实例。可知最后中间件 dispatch 时输出的结果如下:

logger start
test start
test end
logger end

3、小结

  • 中间件本质对是 store 的增强,准确来说是最 store 中的 dispatch 的增强;
  • 中间件能够形成组合链,依赖于 next(action) 的层层传递;
  • 中间件的调用符合洋葱模型,由外到里再由里到外。

Redux源码分析(1) - Redux介绍及使用

redux源码分析(2) - createStore

Redux源码分析(3) - applyMiddleware

Redux源码分析(4) - combineReducers和 bindActionCreators