如何編寫一個 Redux 中間件

要想實現一個 redux 中間件咱們必須瞭解 redux 的基本實現原理。本文將從 redux 源碼入手,重點講解 applyMiddleware 如何將中間件串聯執行。只有理解了底層原理咱們才能夠遊刃有餘的寫出一個 redux 中間件。javascript

目錄

createStore 源碼解讀

redux 經過 createStore 來建立一個 store 對象html

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

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 傳入 enhancereact

return enhancer(createStore)(reducer, preloadedState)ios

也就是說,如今咱們將用 enhance 來建立一個 store 對象。json

applyMiddlewave 源碼解讀

通常狀況下 createStore 的第三個參數 enhance 就是 applyMiddlewaveredux

applyMiddlewave 的代碼只有二十多行倒是本文的重點axios

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 入門教程

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

若是你喜歡的話,可不能夠給我點個當心心(*^__^*) 嘻嘻……

相關文章
相關標籤/搜索