Redux Middleware原理淺析
本文轉(zhuǎn)載自微信公眾號「 之家前端共享流」,作者張俊領(lǐng)。轉(zhuǎn)載本文請聯(lián)系之家前端共享流公眾號。
Redux 是一個基于Flux架構(gòu)的JavaScript 應(yīng)用狀態(tài)管理庫,提供可預(yù)測性的狀態(tài)管理方案。其中,middleware更是Redux中一個重要的概念,它存在使得Redux應(yīng)用更加靈活、可擴(kuò)展、可維護(hù)。本文中,我們將探討 Redux middleware的運行機(jī)制和實現(xiàn)原理,最后帶您輕松實現(xiàn)一個自己的middleware。無論你是初學(xué)者還是有一定經(jīng)驗的開發(fā)者,相信本文都能給你帶來一些新的啟示和技巧。讓我們一起探索Redux middleware的魅力吧!
什么是Middleware
Redux middleware是一種可插拔的機(jī)制,用于在 Redux 的dispatch函數(shù)被調(diào)用后, redcer處理action之前,對action 進(jìn)行攔截、變換、增強(qiáng)等操作。Redux middleware可以用于很多場景,例如:
異步操作:Redux本身是同步的,但是我們可以使用middleware來處理異步操作,例如發(fā)起網(wǎng)絡(luò)請求,等待數(shù)據(jù)返回后再更新store;
日志:用于記錄每個action的執(zhí)行過程,以便于調(diào)試和分析;
認(rèn)證和授權(quán):可以攔截所有action,然后進(jìn)行認(rèn)證和授權(quán),以確保只有授權(quán)用戶可以執(zhí)行某些操作。
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本質(zhì)上就是一個接受store、next、action三個參數(shù)的函數(shù)。其中,store是Redux的store對象,next是dispatch函數(shù),action是當(dāng)前的action對象。
使用Middleware
在Redux中使用middleware非常簡單,只需要在創(chuàng)建store的時候使用applyMiddleware函數(shù)將middleware應(yīng)用到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函數(shù)將middleware1,middleware2應(yīng)用到store上。這樣,當(dāng)我們調(diào)用 store.dispatch(action) 時,middleware就會被依次執(zhí)行,直到reducer處理action。
Middleware內(nèi)部運行機(jī)制及原理剖析
我們通過上文的使用方式發(fā)現(xiàn),middleware是通過createStore來增強(qiáng)和擴(kuò)展原來的dispatch。下面我們就從createStore入手,逐步對middleware進(jìn)行剖析:
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方法接收三個參數(shù):reducer、preloadedState和enhancer。如果傳入了enhancer則使用enhancer來增強(qiáng)store(實際上是通過重寫createStore來增強(qiáng)dispatch),否則就返回一個包含getState、dispatch和subscribe方法的store對象。其中,這里的第三個參數(shù)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
}
}
}
如上所示,先通過輪詢執(zhí)行middleware柯里化函數(shù)第一層來為每個middleware函數(shù)提供getState和dispatch;再通過compose將所有middleware串聯(lián)起來形成一個函數(shù)鏈,從而實現(xiàn)對Redux數(shù)據(jù)的攔截和處理,并最終返回一個增強(qiáng)版的dispatch。我們看到在applyMiddleWare中compose是核心邏輯,下面我們具體分析下compose是如何進(jìn)行middleware函數(shù)聚合的。
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數(shù)組的長度,如果長度為0,則直接返回一個函數(shù);如果長度為1,則直接返回funcs[0];如果長度大于1,則使用reduce方法通過把后一個的middleware的結(jié)果當(dāng)成參數(shù)傳遞給下一個middleware的方式將funcs數(shù)組中的函數(shù)依次組合起來。這里的func也就是接收next即dispatch作為參數(shù)的middleware柯里化函數(shù)第二層,func執(zhí)行后會返回一個新函數(shù)action => next(action)。最終compose返回一個新函數(shù),并按照從右到左的順序依次調(diào)用每個func進(jìn)行處理,這個函數(shù)就是增強(qiáng)版的dispatch。
接下來,我們可以用“把大象放冰箱”這個哲理題作為一個示例,來繼續(xù)加深對compose函數(shù)的理解:
function putElephantInFridge(){
console.log('打開冰箱門');
console.log('把大象放進(jìn)去');
console.log('關(guān)上冰箱門');
}
這個函數(shù)實現(xiàn)起來雖然簡單,但不好進(jìn)行繼續(xù)擴(kuò)展。為了便于擴(kuò)展我們把這個大函數(shù)拆解并抽象化,讓每個函數(shù)都是獨立的,只負(fù)責(zé)完成自己的任務(wù),最后再實現(xiàn)一個通用函數(shù)來獲取最后的結(jié)果:
function openFridgeDoor() {
console.log('打開冰箱門');
}
function putSomethingInFridge(something) {
console.log(`把${something}放進(jìn)去`);
}
function closeFridgeDoor() {
console.log('關(guān)上冰箱門');
}
const putInFridge = (something)=>compose(closeFridgeDoor,()=>{putSomethingInFridge(something)},openFridgeDoor)();
const putInFridgeAndNotClose = (something)=>compose(()=>{putSomethingInFridge(something)},openFridgeDoor)();
putInFridge('牛奶'); // 打開冰箱門 把牛奶放進(jìn)去 關(guān)上冰箱門
putInFridgeAndNotClose('蘋果'); // 打開冰箱門 把蘋果放進(jìn)去
在上面的代碼中,我們使用compose函數(shù)將三個單獨的函數(shù)組合成了一個函數(shù)putInFridge,該函數(shù)接收一個參數(shù)something,并依次執(zhí)行三個步驟,最終將something放進(jìn)了冰箱中。另外,我們也可以將其中兩個函數(shù)組合成函數(shù)putInFridgeAndNotClose。由上我們看到,compose函數(shù)是非常實用的一個函數(shù),通過它可以將任意多個函數(shù)組合在一起,實現(xiàn)更加靈活和有序的函數(shù)調(diào)用,增強(qiáng)了程序的復(fù)用性、可讀性、可測性。
Middleware實現(xiàn)方式
在 Redux 應(yīng)用中,我們可以使用多種方式來實現(xiàn)middleware。下面我們將介紹兩種主要的實現(xiàn)方法:
基于洋蔥模型實現(xiàn)
用過express、koa同學(xué)應(yīng)該都知道它們也都有middleware概念,Redux middleware的實現(xiàn)和koa的洋蔥模型的機(jī)制相似。Redux middleware在dispatch action和到達(dá)reducer之間提供第三方擴(kuò)展點,這種實現(xiàn)方式的代碼結(jié)構(gòu)類似于洋蔥,形成了一層層的包裹,每一層都可以執(zhí)行一些操作,在每一層中可以對action進(jìn)行處理。
基于裝飾器實現(xiàn)
基于裝飾器相對于基于洋蔥模型更加直觀和易于理解,但是它需要使用 ES7 中的裝飾器語法,需要做一定的兼容性處理,這里不做過多闡述。
編寫自定義Middleware
基于以上簡要剖析,我們接下來可以進(jìn)行開發(fā)屬于自己的middleware。下文是一個最簡單的middleware:
const loggerMiddleware = storeAPI => next => action => {
console.log('dispatching', action)
let result = next(action)
console.log('next state', storeAPI.getState())
return result
}
這個簡易版的logger負(fù)責(zé)在控制臺中打印出當(dāng)前動作的類型及當(dāng)狀態(tài)發(fā)生變化時打印出最新的狀態(tài)。使用它可以幫助開發(fā)人員更快地發(fā)現(xiàn)應(yīng)用中的異常。
為什么要使用storeAPI => next => action =>這種形式呢?
要回答這個問題我們可以先來看下Redux三大原則:
- 單一數(shù)據(jù)源
- state是只讀
- 使用reducer純函數(shù)進(jìn)行更改
Redux middleware的storeAPI參數(shù)包含了整個Redux store的狀態(tài)和dispatch方法,這保證了Redux應(yīng)用中只有一個單一的數(shù)據(jù)源;middleware中的狀態(tài)是只讀的,不能被直接修改狀態(tài);Redux middleware中的next函數(shù)它接收一個動作作為參數(shù),并返回一個新的函數(shù)。因此,采用這種形式正是更好的遵循Redux的設(shè)計原則,確保Redux應(yīng)用程序的可預(yù)測性、可維護(hù)性和可擴(kuò)展性。另外,在Redux社區(qū)中也有對使用這種形式的不同聲音,他們認(rèn)為“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”,關(guān)于這塊您可以自行擴(kuò)展閱讀。
總結(jié)
middleware是 Redux 應(yīng)用中的一個重要概念,Redux middleware的原理是基于Redux原則和函數(shù)式編程思想,通過函數(shù)柯里化和函數(shù)組合來實現(xiàn)對dispatch的增強(qiáng),使得在數(shù)據(jù)流傳遞過程中可以插入一些自定義的操作。最后,希望本文能夠幫助讀者加深對middleware原理的理解,助您開發(fā)出更加穩(wěn)定、高效的react應(yīng)用。
參考:https://redux.js.org/tutorials/fundamentals/part-4-store#middlewarehttps://github.com/reduxjs/redux
原文鏈接:https://mp.weixin.qq.com/s/JDM9GOYvkHui7lpwINxL8g