在大部分 React 專案中,都少不了 Redux 的身影,即使一直出現各種被認為有可能取代的 Redux 的語法,例如 Context API(該不該用context api 來取代 redux?)、Hooks API(在 Hooks 中以 useContext 與 useReducer 實現 Redux)等等,Redux 仍然是 React 開發者最愛用的狀態管理工具之一,它之所以被眾多開發者採用是因其具有以下優點:
- 可預測(Predictable):Redux 的運作基於三大原則,開發者可以清楚的知道狀態為何被更新而且如何被更新。
- 易除錯(Debuggable):官方提供不同形式的 DevTools (例如 Chrome Extension)讓開發者使用圖形化介面除錯。
- 彈性大(Flexible):輕量的 Redux 僅提供核心功能,開發者能夠從豐富的第三方套件中根據需求選擇合適的來進行擴充。
一個開發者要能夠正確的使用 Redux,就要先掌握 Store、Action、Reducer 這些基本概念,如果想要進一步透過 Redux 處理非同步行為、API 請求、Logging 等等,就要學會使用 Redux Middleware。
Redux Middleware 的 FAQ
- Middleware 的功能是什麼?它在 Redux 中扮演什麼角色?如何使用 Middleware?
— Middleware 的基本介紹 - 如何建立一個 Middleware?Middleware 中的 next 是做什麼的?Middleware 中呼叫 next 的返回值是什麼?在 Middleware 中一定要有 return 嗎?可以在 Middleware 中取得當前的 state 嗎?在 Middleware 中呼叫 store.dispatch 會發生什麼事?
— Middleware 的使用方式 - 為什麼 Middleware 要宣告成
store => next => action => {}
的形式?Redux 如何套用多個 Middleware?
— Middleware 的運作原理
如果你能夠完整且正確的回答以上的問題,那恭喜你!你非常熟悉 Redux Middleware;但如果你心中也有同樣的疑問,我將這些問題分成三個主題,你可以直接跳到問題對應的主題,當然你也可以照順序閱讀,希望以下的內容可以解開你心中的疑問。
Middleware 的基本介紹
Middleware 的功能是什麼?
Middleware 俗稱中介軟體,用更同俗一點的講法又稱為中介層或中間層,在許多後端伺服器框架中都可以看到 Middleware 的應用,例如針對接受到的 request 在進入 route 前進行解析、修改等動作。簡單來說,Middleware 可以在預先定義好的起、終點之下,針對所有從起點到終點的東西進行處理。
它在 Redux 中扮演什麼角色?
Redux Middleware 可以在 Action 被指派後(起點)進到 Reducer 前(終點)去進行額外的處理,例如呼叫 API(如下圖)等等。
如何使用 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);
- 當 store dispatch 一個 action,如果有 Middleware 則由第一個 Middleware 處理,否則直接交由 Reducer 來處理
- middleware1 為第一個 Middleware,接到 store dispatch 的 action,呼叫 next 將 action 交給下一個 Middleware(middleware2)處理
- middleware2 接到 middleware1 dispatch 的 action,呼叫 next 將 action 交給下一個 Middleware 處理
- 因為 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);
- 最後一個 Middleware(middleware2)呼叫 next 會返回傳入的 action
- middleware1 呼叫 next 會返回下一個 Middleware(middleware2)return 的值
- 最後 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(從以下得這張圖可以更清楚的理解兩者的差異)。
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 來解決問題。
歡迎指教與建議,如果喜歡我的文章,請不要吝嗇給我點鼓掌👏!
參考資料: