middleware 的由來
在業務中需要列印每一個 action 資訊來除錯,又或者希望 dispatch 或 reducer 擁有非同步請求的功能。面對這些場景時,一個個修改 dispatch 或 reducer 程式碼有些乏力,我們需要一個可組合的、自由增減的外掛機制,Redux 借鑑了 Koa 中 middleware 的思想,利用它我們可以在前端應用中便捷地實現如日誌列印、非同步請求等功能。
比如在專案中,進行了如下呼叫後,redux 就整合了 thunk 函式呼叫以及列印日誌的功能。
import thunk from 'redux-thunk'
import logger from '../middleware/logger'
const enhancer = applyMiddleware(thunk, logger), // 以 redux-thunk、logger 中介軟體為例介紹中介軟體的使用
const store = createStore(rootReducer, enhancer)
複製程式碼
下面追本溯源,來分析下原始碼。
applyMiddleware 呼叫入口
export default function createStore(reducer, preloadedState, enhancer) {
// 通過下面程式碼可以發現,如果 createStore 傳入 2 個引數,第二個引數相當於就是 enhancer
if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
enhancer = preloadedState
preloadedState = undefined
}
if (typeof enhancer !== 'undefined') {
return enhancer(createStore)(reducer, preloadedState)
}
...
}
複製程式碼
由上述 createStore 原始碼發現,applyMiddleware 會進行 applyMiddleware(thunk, logger)(createStore)(reducer, preloadedState)
的呼叫。
export default function applyMiddleware(...middlewares) {
return createStore => (...args) => {
const store = createStore(...args)
let dispatch = store.dispatch
let chain = []
const middlewareAPI = {
getState: store.getState, // 呼叫 redux 原生方法,獲取狀態
dispatch: (...args) => dispatch(...args) // 呼叫 redux 原生 dispatch 方法
}
// 序列 middleware
chain = middlewares.map(middleware => middleware(middlewareAPI))
dispatch = compose(...chain)(store.dispatch)
return {
...store,
dispatch // 返回加工過的 dispatch
}
}
}
複製程式碼
可以發現 applyMiddleware 的作用其實就是返回加工過的 dispatch,下面會著重分析 middlewares 是如何序列起來的以及 dispatch 是如何被加工的。
序列 middleware
const middlewareAPI = {
getState: store.getState,
dispatch: (...args) => dispatch(...args)
}
chain = middlewares.map(middleware => middleware(middlewareAPI))
dispatch = compose(...chain)(store.dispatch)
複製程式碼
觀察上述程式碼後發現每個 middleware 都會傳入引數 middlewareAPI,來看下中介軟體 logger 的原始碼 以及 redux-thunk 的原始碼, 發現中介軟體接受的第一個引數正是 ({ dispatch, getState })
// logger 原始碼
export default ({ dispatch, getState }) => next => action => {
console.log(action)
return next(action) // 經 compose 原始碼分析,此處 next 為 Store.dispatch
}
複製程式碼
// redux-thunk 原始碼
export default ({ dispatch, getState }) => next => action => {
if (typeof action === 'function') {
return action(dispatch)
}
return next(action) // 此處 next 為 logger 中介軟體返回的 (action) => {} 函式
}
複製程式碼
dispatch 是如何被加工的
接著上個小節,在 dispatch = compose(...chain)(store.dispatch)
中發現了 compose 函式,來看下 compose 的原始碼
export default function compose(...funcs) {
// ...
return funcs.reduce((a, b) => (...args) => a(b(...args)))
}
複製程式碼
compose 原始碼中的 funcs.reduce((a, b) => (...args) => a(b(...args)))
算是比較重要的一句,它的作用是返回組合引數後的函式,比如 compose(f, g, h) 等價於 (...args) => f(g(h(...args))),效果圖如下所示,呼叫 this.props.dispatch() 後,會呼叫相應的中介軟體,最終會呼叫 redux 原生的 store.dispatch(),並且可以看到中介軟體呼叫的形式類似資料結構中的棧(先進後出)。
拿上個小節提到的 logger、redux-thunk 中介軟體為例,其 middleware 的內部序列呼叫方式如下,從而完成了 dispatch 功能的增強(支援如 this.props.dispatch(func)
的呼叫以及日誌功能)。具體可以看 專案中的運用
action => {
if (typeof action === 'function') {
return action(dispatch)
}
return (action => {
console.log(action)
return store.dispatch(action)
})(action)
}
複製程式碼
參考文獻
深入React技術棧