Redux 中間件機制探底

原文地址: https://monster1935.com/2019/...

狀態管理方案以前僅僅接觸過 Vuex, 使用 React 開發時,不免要調研一下 React 技術棧下的狀態管理方案,發現有 Redux 和 Mobx 相關流派。如下內容僅針對 Redux 展開討論。javascript

在使用 Redux 的過程當中發現,有這麼幾個知識點仍是比較容易接受:html

  1. 對狀態的修改必須 dispatch 一個 action, 保證狀態的修改可控易管理
  2. reducer 必須是一個純函數,不能對 state 直接進行修改,而是每次返回一個全新的 state。純函數的實現能夠提升運行效率,固定的輸入產生固定的輸出
  3. redux 自己有一個「訂閱」的概念,狀態更改後, Redux 會將依次執行訂閱者,在訂閱者的事件回調函數中能夠經過 store.getState() 拿到最新的狀態

此時有這樣一個疑問: 上面僅僅討論了一個同步的狀況,對於一些異步以及存在其餘反作用的 action 產生過程如何處理,帶着這個疑問,看了官方文檔以及一些 Demo 實現。這個過程出現了 redux-thunk、redux-promise、 redux-saga 等處理方案。這些又是作什麼的,分別都解決了什麼問題?java

這就要討論一下 Redux 的中間件機制,在 Redux 中有這樣一個 API, applyMiddleware, 主要用於註冊 Redux 中的中間件。redux

閱讀文檔的過程當中,主要搞清楚了一個最基本的世界觀問題: Redux 的中間件是用來作什麼的?它提供的是位於 action 被髮起以後,到達 reducer 以前的擴展點。 也就是以上討論的 redux-thunk、redux-promise、redux-saga 等都是一個個的 Redux 的中間件,使用時須要在 Redux createStore 時註冊,他們分別加強了 Redux dispatch 的能力。一樣能夠理解爲:在應用這些中間件後,使用的 dispatch 已經不是 Redux 本來的 dispatch,都是經這些中間件改寫後的 dispatch。這樣咱們就能再真正產生 action 以前作一些反作用的封裝。promise

以 redux-thunk 爲例,咱們能夠清晰的的看清楚這個過程。app

function createThunkMiddleware(extraArgument) {
  return ({ dispatch, getState }) => (next) => (action) => {
    if (typeof action === 'function') {
      return action(dispatch, getState, extraArgument);
    }

    return next(action);
  };
}

const thunk = createThunkMiddleware();
thunk.withExtraArgument = createThunkMiddleware;

export default thunk;

以上就是 redux-thunk 這個庫的源碼部分,「短小精悍」這個詞來形容這個庫一點都不過度了。究其實現,能夠發現,redux-thunk 這個中間件主要是提供了 dispatch 一個 function 的能力。正常來講 Redux 的 dispatch 僅僅能 dispatch 一個純js對象,也就是 action。 使用 redux-thunk 後,咱們能夠接收一個 function, 這個 function 會被獲得調用,並被傳入 dispatch 這個參數,真正的 dispatch 發生在這個 function 內部。異步

至於 Redux 的中間件機制是如何實現的,在看了其源碼實現後,更是巧妙。函數

首先咱們要明確一下 「中間件」 這個概念。我的粗俗理解:中間件就是一個「管道」,只要你過了這個「管道」,都會被這個「管道」接管,「管道」不會攔住不放,而是將你「蹂躪」一番再放了你,固然也有可能不「蹂躪」你,頂多查一下戶口(傳說中的日誌中間件)。凡是經「蹂躪」過的不論是從精神上、仍是肉體上都再也不是原來的你我,多是一蹶不振,也多是奮發圖強。spa

有了以上的理解,當咱們在看這個事情的時候就好理解了。可能會有多箇中間件,只要進了這個「屋」,就要依次經歷這些中間件。prototype

const reduxMiddleware = ({dispatch, getState}) => (next) => (action) => {
  // 作一些查戶口以及蹂躪相關的事情 
  
  // 放行
  return next(action);
}

這是一個 redux middleware 的通用實現。當咱們在 applyMiddleware 時發生了什麼?

const chain = middlewares.map(middleware => middleware({
  getState: store.getState,
  dispatch: (action, ...args) => dispatch(action, ...args),
}));

const dispatch = compose(chain)(store.dispatch);
return {
  ...store,
  dispatch
}

大體意思就是將全部的 middleware 傳入,並經過 compose 這個函數將全部的中間件組合並返回一個 dispatch 函數, 此時的 dispatch 不是 redux 本來的 dispatch 實現,而是一個經中間件加強了的 dispatch,這裏面有一個控制權的反轉,即將本來的 dispatch 功能做爲參數傳入,在函數內部完成 dispatch 的邏輯。此時的 dispatch 多是這樣的:

const dispatch = (dispatch) => {
  // do things
  // 這裏通過了全部註冊過的中間件的處理
  // do things
  return action => dispatch(action);
}

Redux 中間件實現的關鍵是 compose 函數,compose 函數利用 Array.prototype.reduce() API,完成全部中間件函數的依次調用,並返回如上所示的一個函數。

function compose(funcs) {
  return function (dispatch) {
    if (funcs.length === 1) {
      return funcs[0];
    }
    return funcs.reduce((a,b) => (...args) => a((b(...args))));
  }
}

如上即是整個中間件機制的實現過程。由於中間涉及到一些函數柯里化的內容,有些函數嵌套較深才能返回,若是感受到晦澀,能夠看這個簡潔版的代碼:

// 這是一箇中間件
const a = (next) => (action) => {
  console.log('通過了 a 中間件的蹂躪');
  return next(action);
};

// 這是一箇中間件
const b = (next) => (action) => {
  console.log('通過了 b 中間件的蹂躪');
  return next(action);
};

// 這是一箇中間件
const c = (next) => (action) => {
  console.log('通過了 c 中間件的蹂躪');;
  return next(action);
};

// 原版的 dispatch
var rawDispatch = (action) => {
  console.log('終於輪到原生的dispatch action了,派發了: ', action);
  return action;
}

/** 如下是 applyMiddlware 的實現原理, 開始註冊中間件 */
var arr = [a, b, c];

var res = arr.reduce((a, b) => (...args) => a(b(...args)));

var enhanceDispatch = res(rawDispatch);


// 調用一個加強的dispatch,會發現中間件邏輯會一次處理
enhanceDispatch('add');

// 通過了 a 中間件的蹂躪
// 通過了 b 中間件的蹂躪
// 通過了 c 中間件的蹂躪
// 終於輪到原生的 dispatch action了,派發了:  add
// "add"

參考連接:

  1. https://www.redux.org.cn/docs...
相關文章
相關標籤/搜索