Redux Middleware原理淺析
本文轉載自微信公眾號「 之家前端共享流」,作者張俊領。轉載本文請聯系之家前端共享流公眾號。
Redux 是一個基于Flux架構的JavaScript 應用狀態管理庫,提供可預測性的狀態管理方案。其中,middleware更是Redux中一個重要的概念,它存在使得Redux應用更加靈活、可擴展、可維護。本文中,我們將探討 Redux middleware的運行機制和實現原理,最后帶您輕松實現一個自己的middleware。無論你是初學者還是有一定經驗的開發者,相信本文都能給你帶來一些新的啟示和技巧。讓我們一起探索Redux middleware的魅力吧!
什么是Middleware
Redux middleware是一種可插拔的機制,用于在 Redux 的dispatch函數被調用后, redcer處理action之前,對action 進行攔截、變換、增強等操作。Redux middleware可以用于很多場景,例如:
異步操作:Redux本身是同步的,但是我們可以使用middleware來處理異步操作,例如發起網絡請求,等待數據返回后再更新store;
日志:用于記錄每個action的執行過程,以便于調試和分析;
認證和授權:可以攔截所有action,然后進行認證和授權,以確保只有授權用戶可以執行某些操作。
middleware簡化后的核心邏輯如下:
const middleware = store => next => action => {
// do something before dispatching the action
const result = next(action);
// do something after dispatching the action
return result;
};
通過以上代碼可以看出middleware本質上就是一個接受store、next、action三個參數的函數。其中,store是Redux的store對象,next是dispatch函數,action是當前的action對象。
使用Middleware
在Redux中使用middleware非常簡單,只需要在創建store的時候使用applyMiddleware函數將middleware應用到store上即可,
例如:
import { createStore, applyMiddleware } from 'redux'import rootReducer from './reducers'
import middleware1 from './middleware/middleware1'
import middleware2 from './middleware/middleware2'
const store = createStore(
rootReducer,
applyMiddleware(middleware1, middleware2)
)
在上面的代碼中,我們使用了applyMiddleware函數將middleware1,middleware2應用到store上。這樣,當我們調用 store.dispatch(action) 時,middleware就會被依次執行,直到reducer處理action。
Middleware內部運行機制及原理剖析
我們通過上文的使用方式發現,middleware是通過createStore來增強和擴展原來的dispatch。下面我們就從createStore入手,逐步對middleware進行剖析:
createStore源碼分析
//簡化后的源碼
import { Action } from './types/actions'
import { Reducer } from './types/reducers'
export function createStore<
S,
A extends Action,
Ext extends {} = {},
StateExt extends {} = {}
>(
reducer: Reducer<S, A>,
preloadedState?: PreloadedState<S> | StoreEnhancer<Ext, StateExt>,
enhancer?: StoreEnhancer<Ext, StateExt>
): Store<S, A, StateExt> & Ext {
if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
enhancer = preloadedState as StoreEnhancer<Ext, StateExt>
preloadedState = undefined
}
if (typeof enhancer !== 'undefined') {
if (typeof enhancer !== 'function') {
throw new Error(
`Expected the enhancer to be a function. Instead, received: '${kindOf(enhancer)}'`
)
}
return enhancer(createStore)(
reducer,
preloadedState as PreloadedState<S>
) as Store<S, A, StateExt> & Ext
}
let currentReducer = reducer
let currentState = preloadedState as S
let currentListeners: Map<number, ListenerCallback> | null = new Map()
let nextListeners = currentListeners
let listenerIdCounter = 0
let isDispatching = false
function ensureCanMutateNextListeners() {
if (nextListeners === currentListeners) {
nextListeners = new Map()
currentListeners.forEach((listener, key) => {
nextListeners.set(key, listener)
})
}
}
function getState(): S {
...
}
function subscribe(listener: () => void) {
...
let isSubscribed = true
ensureCanMutateNextListeners()
const listenerId = listenerIdCounter++
nextListeners.set(listenerId, listener)
return function unsubscribe() {
if (!isSubscribed) {
return
}
if (isDispatching) {
throw new Error('...')
}
isSubscribed = false
ensureCanMutateNextListeners()
nextListeners.delete(listenerId)
currentListeners = null
}
}
function dispatch(action: A) {
...
}
dispatch({ type: ActionTypes.INIT } as A)
const store = {
dispatch: dispatch as Dispatch<A>,
subscribe,
getState
} as unknown as Store<S, A, StateExt> & Ext
return store
}
從以上代碼,createStore方法接收三個參數:reducer、preloadedState和enhancer。如果傳入了enhancer則使用enhancer來增強store(實際上是通過重寫createStore來增強dispatch),否則就返回一個包含getState、dispatch和subscribe方法的store對象。其中,這里的第三個參數enhancer就是我們下文要分析的applyMiddleWare
applyMiddleware源碼分析
//簡化后的源碼
export default function applyMiddleware(
...middlewares: Middleware[]
): StoreEnhancer<any> {
return createStore =>
<S, A extends AnyAction>(
reducer: Reducer<S, A>,
preloadedState?: PreloadedState<S>
) => {
const store = createStore(reducer, preloadedState)
let dispatch: Dispatch = () => {
throw new Error(
'Dispatching while constructing your middleware is not allowed. ' +
'Other middleware would not be applied to this dispatch.'
)
}
const middlewareAPI: MiddlewareAPI = {
getState: store.getState,
dispatch: (action, ...args) => dispatch(action, ...args)
}
const chain = middlewares.map(middleware => middleware(middlewareAPI))
dispatch = compose<typeof dispatch>(...chain)(store.dispatch)
return {
...store,
dispatch
}
}
}
如上所示,先通過輪詢執行middleware柯里化函數第一層來為每個middleware函數提供getState和dispatch;再通過compose將所有middleware串聯起來形成一個函數鏈,從而實現對Redux數據的攔截和處理,并最終返回一個增強版的dispatch。我們看到在applyMiddleWare中compose是核心邏輯,下面我們具體分析下compose是如何進行middleware函數聚合的。
compose源碼分析
export default function compose(...funcs: Function[]) {
if (funcs.length === 0) {
// infer the argument type so it is usable in inference down the line
return <T>(arg: T) => arg
}
if (funcs.length === 1) {
return funcs[0]
}
return funcs.reduce((a, b) => (...args) => a(b(...args)));
}
如上所示,這段代碼首先判斷funcs數組的長度,如果長度為0,則直接返回一個函數;如果長度為1,則直接返回funcs[0];如果長度大于1,則使用reduce方法通過把后一個的middleware的結果當成參數傳遞給下一個middleware的方式將funcs數組中的函數依次組合起來。這里的func也就是接收next即dispatch作為參數的middleware柯里化函數第二層,func執行后會返回一個新函數action => next(action)。最終compose返回一個新函數,并按照從右到左的順序依次調用每個func進行處理,這個函數就是增強版的dispatch。
接下來,我們可以用“把大象放冰箱”這個哲理題作為一個示例,來繼續加深對compose函數的理解:
function putElephantInFridge(){
console.log('打開冰箱門');
console.log('把大象放進去');
console.log('關上冰箱門');
}
這個函數實現起來雖然簡單,但不好進行繼續擴展。為了便于擴展我們把這個大函數拆解并抽象化,讓每個函數都是獨立的,只負責完成自己的任務,最后再實現一個通用函數來獲取最后的結果:
function openFridgeDoor() {
console.log('打開冰箱門');
}
function putSomethingInFridge(something) {
console.log(`把${something}放進去`);
}
function closeFridgeDoor() {
console.log('關上冰箱門');
}
const putInFridge = (something)=>compose(closeFridgeDoor,()=>{putSomethingInFridge(something)},openFridgeDoor)();
const putInFridgeAndNotClose = (something)=>compose(()=>{putSomethingInFridge(something)},openFridgeDoor)();
putInFridge('牛奶'); // 打開冰箱門 把牛奶放進去 關上冰箱門
putInFridgeAndNotClose('蘋果'); // 打開冰箱門 把蘋果放進去
在上面的代碼中,我們使用compose函數將三個單獨的函數組合成了一個函數putInFridge,該函數接收一個參數something,并依次執行三個步驟,最終將something放進了冰箱中。另外,我們也可以將其中兩個函數組合成函數putInFridgeAndNotClose。由上我們看到,compose函數是非常實用的一個函數,通過它可以將任意多個函數組合在一起,實現更加靈活和有序的函數調用,增強了程序的復用性、可讀性、可測性。
Middleware實現方式
在 Redux 應用中,我們可以使用多種方式來實現middleware。下面我們將介紹兩種主要的實現方法:
基于洋蔥模型實現
用過express、koa同學應該都知道它們也都有middleware概念,Redux middleware的實現和koa的洋蔥模型的機制相似。Redux middleware在dispatch action和到達reducer之間提供第三方擴展點,這種實現方式的代碼結構類似于洋蔥,形成了一層層的包裹,每一層都可以執行一些操作,在每一層中可以對action進行處理。
基于裝飾器實現
基于裝飾器相對于基于洋蔥模型更加直觀和易于理解,但是它需要使用 ES7 中的裝飾器語法,需要做一定的兼容性處理,這里不做過多闡述。
編寫自定義Middleware
基于以上簡要剖析,我們接下來可以進行開發屬于自己的middleware。下文是一個最簡單的middleware:
const loggerMiddleware = storeAPI => next => action => {
console.log('dispatching', action)
let result = next(action)
console.log('next state', storeAPI.getState())
return result
}
這個簡易版的logger負責在控制臺中打印出當前動作的類型及當狀態發生變化時打印出最新的狀態。使用它可以幫助開發人員更快地發現應用中的異常。
為什么要使用storeAPI => next => action =>這種形式呢?
要回答這個問題我們可以先來看下Redux三大原則:
- 單一數據源
- state是只讀
- 使用reducer純函數進行更改
Redux middleware的storeAPI參數包含了整個Redux store的狀態和dispatch方法,這保證了Redux應用中只有一個單一的數據源;middleware中的狀態是只讀的,不能被直接修改狀態;Redux middleware中的next函數它接收一個動作作為參數,并返回一個新的函數。因此,采用這種形式正是更好的遵循Redux的設計原則,確保Redux應用程序的可預測性、可維護性和可擴展性。另外,在Redux社區中也有對使用這種形式的不同聲音,他們認為“there is essentially no need to split the arguments into different function calls”、“minor change could promote authors' familiarity and understanding, thus encourage the development of additional middleware to Redux”,關于這塊您可以自行擴展閱讀。
總結
middleware是 Redux 應用中的一個重要概念,Redux middleware的原理是基于Redux原則和函數式編程思想,通過函數柯里化和函數組合來實現對dispatch的增強,使得在數據流傳遞過程中可以插入一些自定義的操作。最后,希望本文能夠幫助讀者加深對middleware原理的理解,助您開發出更加穩定、高效的react應用。
參考:https://redux.js.org/tutorials/fundamentals/part-4-store#middlewarehttps://github.com/reduxjs/redux
原文鏈接:https://mp.weixin.qq.com/s/JDM9GOYvkHui7lpwINxL8g