Redux源碼分析(3) - applyMiddleware

1、applyMiddleware 介紹

   applyMiddleware 作爲 Redux 的核心 api 之一,本質就是在 dispatch 更改 reducer 之前做一些操作,具體的實現其實就對 store 的增強,其中最終是對 store 中的 dispatch 的增強。關於 applyMiddleware 的使用可參考上一章節 Redux源碼分析(1) - Redux介紹及使用 和 官方文檔 applyMiddleware

Middleware 可以讓你包裝 store 的 dispatch 方法來達到你想要的目的。同時, middleware 還擁有“可組合”這一關鍵特性。多個 middleware 可以被組合到一起使用,形成 middleware 鏈。其中,每個 middleware 都不需要關心鏈中它前後的 middleware 的任何信息。

  applyMiddleware 的源碼結構圖如下圖所示。

applyMiddleware

  首先看下 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 在執行過程中,實際是:

dispathc的洋蔥模型

   其執行過程對應的就是洋蔥模型,先從外到裏層層深入直到最裏層,然後從最裏層層往外,直到最外層。

   結合本文中實例。可知最後中間件 dispatch 時輸出的結果如下:

logger start
test start
test end
logger end

3、小結

  • 中間件本質對是 store 的增強,準確來說是最 store 中的 dispatch 的增強;
  • 中間件能夠形成組合鏈,依賴於 next(action) 的層層傳遞;
  • 中間件的調用符合洋蔥模型,由外到裏再由裏到外。

Redux源碼分析(1) - Redux介紹及使用
redux源碼分析(2) - createStore
Redux源碼分析(3) - applyMiddleware

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章