詳解 Redux Middleware

谷哥
11 min readDec 26, 2019

--

在大部分 React 專案中,都少不了 Redux 的身影,即使一直出現各種被認為有可能取代的 Redux 的語法,例如 Context API(該不該用context api 來取代 redux?)、Hooks API(在 Hooks 中以 useContext 與 useReducer 實現 Redux)等等,Redux 仍然是 React 開發者最愛用的狀態管理工具之一,它之所以被眾多開發者採用是因其具有以下優點:

  1. 可預測(Predictable):Redux 的運作基於三大原則,開發者可以清楚的知道狀態為何被更新而且如何被更新。
  2. 易除錯(Debuggable):官方提供不同形式的 DevTools (例如 Chrome Extension)讓開發者使用圖形化介面除錯。
  3. 彈性大(Flexible):輕量的 Redux 僅提供核心功能,開發者能夠從豐富的第三方套件中根據需求選擇合適的來進行擴充。

一個開發者要能夠正確的使用 Redux,就要先掌握 Store、Action、Reducer 這些基本概念,如果想要進一步透過 Redux 處理非同步行為、API 請求、Logging 等等,就要學會使用 Redux Middleware。

Redux Middleware 的 FAQ

  1. Middleware 的功能是什麼?它在 Redux 中扮演什麼角色?如何使用 Middleware?
    — Middleware 的基本介紹
  2. 如何建立一個 Middleware?Middleware 中的 next 是做什麼的?Middleware 中呼叫 next 的返回值是什麼?在 Middleware 中一定要有 return 嗎?可以在 Middleware 中取得當前的 state 嗎?在 Middleware 中呼叫 store.dispatch 會發生什麼事?
    — Middleware 的使用方式
  3. 為什麼 Middleware 要宣告成 store => next => action => {} 的形式?Redux 如何套用多個 Middleware?
    — Middleware 的運作原理

如果你能夠完整且正確的回答以上的問題,那恭喜你!你非常熟悉 Redux Middleware;但如果你心中也有同樣的疑問,我將這些問題分成三個主題,你可以直接跳到問題對應的主題,當然你也可以照順序閱讀,希望以下的內容可以解開你心中的疑問。

Middleware 的基本介紹

Middleware 的功能是什麼?

Middleware 俗稱中介軟體,用更同俗一點的講法又稱為中介層或中間層,在許多後端伺服器框架中都可以看到 Middleware 的應用,例如針對接受到的 request 在進入 route 前進行解析、修改等動作。簡單來說,Middleware 可以在預先定義好的起、終點之下,針對所有從起點到終點的東西進行處理

它在 Redux 中扮演什麼角色?

Redux Middleware 可以在 Action 被指派後(起點)進到 Reducer 前(終點)去進行額外的處理,例如呼叫 API(如下圖)等等。

https://miro.medium.com/max/760/0*Q4lgrUkqEFvvoUzZ

如何使用 Middleware?

將要使用的一個或多個 Middleware 傳入 Redux 提供的 applyMiddleware 函式會返回一個 Enhancer,再將這個 Enhancer 傳入 createStore 的第三個參數即可。

import { applyMiddleware, createStore } from 'redux';const enhancer = applyMiddleware(
middlewareOne,
middlewareTwo
);
const store = createStore(
reducers,
initialState,
enhancer
);

Middleware 的使用方式

如何建立一個 Middleware?

function middleware(store) {
return function(next) {
return function(action) {

/* Code */

return next(action);
}
}
}

上面是一個 Dummy Middleware,表示一個 Middleware 的基本架構,也可以用 ES6 Arrow Function 的方式來表示:

const middleware = store => next => action => {  /* Code */  return next(action);
}

Middleware 中的 next 是做什麼的?

next 是用來將 Action 交給下一個 Middleware 的處理的函式(換句話說,每一個 Middleware 接到的 Action 都會是前一個 Middleware 呼叫 next 傳入的 Action),如果沒有下一個 Middleware 就會交由 Reducer 處理,以下舉個簡單的例子:

const middleware1 = store => next => action => {
next(action);
};
const middleware2 = store => next => action => {
next(action);
};
const reducer = (state, action) => {}// 建立 store & 套用 middlewarestore.dispatch(action);
  1. 當 store dispatch 一個 action,如果有 Middleware 則由第一個 Middleware 處理,否則直接交由 Reducer 來處理
  2. middleware1 為第一個 Middleware,接到 store dispatch 的 action,呼叫 next 將 action 交給下一個 Middleware(middleware2)處理
  3. middleware2 接到 middleware1 dispatch 的 action,呼叫 next 將 action 交給下一個 Middleware 處理
  4. 因為 middleware2 是最後一個 Middleware,所以由 reducer 處理 middleware2 dispatch 的 action

Middleware 中呼叫 next 的返回值是什麼?

每一個 Middleware return 的值會在前一個 Middleware 中呼叫 next 取得,也就是說,在 Middleware 中呼叫 next 的返回值會是下一個 Middleware return 的值,如果是在最後一個 Middleware 中呼叫 next,返回值會是傳入的 action 本身,例如:

const middleware1 = store => next => action => {
const value = next(action);
return value;
};
const middleware2 = store => next => action => {
const value = next(action);
return value;
};
// 建立 store & 套用 middlewareconst value = store.dispatch(action);
  1. 最後一個 Middleware(middleware2)呼叫 next 會返回傳入的 action
  2. middleware1 呼叫 next 會返回下一個 Middleware(middleware2)return 的值
  3. 最後 store.dispatch 返回的會是第一個 Middleware(middleware1)return 的值

以下附上完整的範例程式碼,示範呼叫 next 的執行順序和其返回值的執行結果。建議可以先看一遍程式碼,想想看最後會印出什麼東西?再打開下面的 Console 就可以看到答案囉!

在 Middleware 中一定要有 return 嗎?

不一定。如果 Middleware 中沒有 return,那在上一個 Middleware 中呼叫 next 就會返回 undefined(如果是第一個 Middleware 中沒有 return,那呼叫 store.dispatch 就會返回 undefined),一般來說 Middleware 可以 return 任何型態的資料,但如果沒有特別的需求建議直接 return 呼叫 next 返回的值,確保呼叫 store.dispatch 可以取得 dispatch 的 action。

可以在 Middleware 中取得當前的 state 嗎?

當然可以。Middleware 會寫成 store => next => action => {} 的其中一個原因就是讓開發者可以在 Middleware 取得 store 參數,透過 store.getState 來取得當前的 state。

在 Middleware 中呼叫 store.dispatch 會發生什麼事?

Middleware 中的 store 參數除了可以呼叫 getState 來取得當前的 state 之外,也可以呼叫 dispatch 來發送一個 action,和呼叫 next 的不同之處在於:透過 next 來 dispatch 的 action 送往下一個 Middleware,而透過 store.dispatch 來 dispatch 的 action 則是會重新經過所有的 Middleware(從以下得這張圖可以更清楚的理解兩者的差異)。

https://miro.medium.com/max/3248/1*vBeR3yXWcukp_yZpNBtHlg.png

Middleware 的運作原理

為什麼 Middleware 要宣告成 store => next => action => {} 的形式?

首先,我們從 Middleware 存在的目的出發。Middleware 要能夠在 Action 被 dispatch 之後、進入 Reducer 之前去處理它,因此 Middleware 應該要是一個能夠取得 action 參數的函式:

function middleware(action) {}

接著,為了能夠串連起多個 Middleware,Middleware 應該要能夠取得一個當前的 dispatch 函式並且返回一個會呼叫該 dispatch 的函式,因此從上面的形式衍生:

function middlewareWrapper(dispatch) {
return function(action) {
dispatch(action)
}
}

最後,為了取得當前的 state 或是重新 dispatch 一個 action,Middleware 需要取得 store 參數,因此再多包覆一層:

function storeWrapper(store)
function middlewareWrapper(dispatch) {
return function(action) {
dispatch(action)
}
}
}

用 ES6 Arrow Function 的形式來改寫:

const = storeWrapper = store => dispatch => action => {
dispatch(action)
}

其中函式和參數名稱改成習慣的命名:

const middleware = store => next => action => {
next(action)
}

就會是 store => dispatch => action => {} 的形式了。

Redux 如何套用多個 Middleware?

Redux 套用多個 Middleware 的邏輯,主要在 applyMiddleware 這個函式內,以下我們來模仿 Redux 官方原始碼寫一個可以套用三個 Middleware 的 applyMiddleware 函式:

function applyMiddleware(middleware1, middleware2, middleware3) {
const store = {
dispatch: action => action
};
const [mw1, mw2, mw3] = [middleware1, middleware2, middleware3].map(middleware => middleware(store)) store.dispatch = mw1(mw2(mw3(store.dispatch))) return store
}

以上程式碼簡化了許多部份(實際上 applyMiddleware 能夠傳入並且套用數量不固定的 Middleware,以及其他比較複雜的邏輯),主要只擷取出套用的作法,但可以傳入三個 Middleware 模擬真實的執行,以下附上完整模擬的程式碼。

雖然大部分時候我們都直接使用別人寫好的 Redux Middleware,很少會自己實作 Redux Middleware,但如果試著去理解 Redux Middleware 背後設計的邏輯,我們就能更好的運用 Redux Middleware 來解決問題。

歡迎指教與建議,如果喜歡我的文章,請不要吝嗇給我點鼓掌👏!

參考資料:

  1. https://github.com/max80713/notes/blob/master/redux/redux-middleware.md
  2. https://redux.js.org/advanced/middleware

--

--

谷哥
谷哥

Written by 谷哥

前端工程師/技術導師

No responses yet