如何編寫一個 Redux 中介軟體

xiexin發表於2017-12-04

要想實現一個 redux 中介軟體我們必須瞭解 redux 的基本實現原理。本文將從 redux 原始碼入手,重點講解 applyMiddleware 如何將中介軟體串聯執行。只有理解了底層原理我們才可以遊刃有餘的寫出一個 redux 中介軟體。

目錄

createStore 原始碼解讀

redux 通過 createStore 來建立一個 store 物件

要理解 applyMiddleware 的實現原理,我們要從 createStore 入手

export default function createStore(reducer, preloadedState, enhancer) {
  if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
    enhancer = preloadedState
    preloadedState = undefined
  }

  if (typeof enhancer !== 'undefined') {
    if (typeof enhancer !== 'function') {
      throw new Error('Expected the enhancer to be a function.')
    }

    return enhancer(createStore)(reducer, preloadedState)
  }

  if (typeof reducer !== 'function') {
    throw new Error('Expected the reducer to be a function.')
  }
  // 篇幅有限,後面被我省略了,有興趣請去看 redux 原始碼
  // ......
複製程式碼

可以看見 createStore 的三個引數依次為: reducer, preloadedState, enhancer。參見原始碼,如果傳入了 enhance 引數且為函式,則將 createStore 傳入 enhance

return enhancer(createStore)(reducer, preloadedState)

也就是說,現在我們將用 enhance 來建立一個 store 物件。

applyMiddlewave 原始碼解讀

一般情況下 createStore 的第三個引數 enhance 就是 applyMiddlewave

applyMiddlewave 的程式碼只有二十多行卻是本文的重點

export default function applyMiddleware(...middlewares) {
  return (createStore) => (reducer, preloadedState, enhancer) => {
    var store = createStore(reducer, preloadedState, enhancer)
    var dispatch = store.dispatch
    var chain = []

    var middlewareAPI = {
      getState: store.getState,
      dispatch: (action) => dispatch(action)
    }
    chain = middlewares.map(middleware => middleware(middlewareAPI))
    dispatch = compose(...chain)(store.dispatch)

    return {
      ...store,
      dispatch
    }
  }
}
複製程式碼

參見 createStore 的原始碼可以得知:applyMiddlewave 依然使用 createStore 建立了store 物件並且返回,只是改寫了這個物件的 dispatch 方法。

下面我們重點來看這個被改寫掉的 dispatch 方法,同時理解它和原生 dispatch 方法的區別也是本文的重點。為了更直觀的瞭解這個過程我們先來看一個 簡單的中介軟體實現 logger middlewave

export default store => next => action => {
    const start = Date.now();
    next(action);
    const ms = Date.now() - start;
    console.log(`dispatch: ${action.type} - ${ms}ms`);
}
複製程式碼

下面分二步詳細探討中介軟體的執行原理

  1. 將原生的 getState 和 dispacth 作為第一個引數傳入中介軟體陣列,獲得執行完的 chain 陣列;

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

  2. 組合串聯 middlewave

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

    compose 將所有的中介軟體串聯起來組成新的 dispatch

    compose 原始碼

    function compose(...funcs) {
        return arg => funcs.reduceRight((composed, f) => f(composed), arg);
    }
    複製程式碼

    參考我們的 logger middlewave 這裡的 composed 即是我們的 next 引數。

    reduceRight 和 ruduce 一樣,不過 reduceRight 是從陣列的右端開始執行,arg 將作為 reduceRight 的初始值(這裡就是 store.dispatch)。假設我們的 chain 陣列為 [f1,f2,f3]執行完畢後 dispatch 為 dispatch = f1(f2(f3(store.dispatch)))),呼叫這個新的 dispatch 每個中介軟體就能依次執行了,這裡的中介軟體執行過程也是類似於 Koa 的中介軟體是非常經典的洋蔥模型。只有最後一箇中介軟體會觸發 redux 原生的 dispatch,將這個 action 分發出去。(沒錯,我就是靈魂畫師)

洋蔥模型

redux-thunk 的實現原理

一般而言 dispatch 只能分發一個 action 物件,但是使用了 redux-thunk 中介軟體我們卻可以分發一個非同步函式。

const thunk = store => next => action => {
    typeof action === 'function' ?
        action(store.dispatch,store.getState) :
        next(action)
}
複製程式碼

一個非同步的 action 的示例

function getMessage = (dispatch, getState) => {
    axios.get('xxx/xxx')
    .then((res) => {
        dispatch({
            type: 'GET_MESSAGE_SUCCESS',
            message: res.json(),
        });
    })
    .catch((err) => {
        dispatch({
            type: 'GET_MESSAGE_ERROR',
            message: 'error'
        });
    });
}
複製程式碼

這裡的 dispatch 任然是改造後的 dispatch 因為傳入中介軟體的第一個引數 store 即 middlewareApi 中的 dispatch 是一個閉包儲存著對最外層函式 dispatch 的引用,所以當 diapatch 被改寫後後面呼叫的 dispatch 都是這個新的 dispatch(即中介軟體的串聯),所以即使在非同步 action 中分發一個 action 依然會將全部中介軟體再執行一遍。

如何編寫一箇中介軟體

所以理解了以上,編寫一箇中介軟體將超級簡單,只需要按照中介軟體編寫規範

function myMiddleware = store => next => action = {
    // 在這裡你可以拿到 store.getState 和 store.dispatch
    // 注意如果你呼叫 store.dispatch 中介軟體又從新從最外層開始 如果不加限制條件將陷入死迴圈
    // do something
    next(action)   // 進入下一個中介軟體,最後一箇中介軟體的 next 引數為 redux 原生 dispatch
    // 返回繼續執行這個中介軟體剩餘部分
}
複製程式碼

總結

深入理解 redux 中介軟體的實現原理,可以讓我們在日常工作中,對 redux 資料流向更加清晰和對自己的程式更加有把握。本人水平有限,如有錯誤還請指出。

參考資料

redux 官方文件

《深入 react 技術棧》

阮一峰 redux 入門教程

原創文章,轉載請註明原地址

如果你喜歡的話,可不可以給我點個小心心(*^__^*) 嘻嘻……

相關文章