原文鏈接
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 個參數,第二個參數相當于就是 enhancerif (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {enhancer = preloadedStatepreloadedState = undefined}if (typeof enhancer !== 'undefined') {return enhancer(createStore)(reducer, preloadedState)}...
}
復制代碼
由上述 createStore 源碼發現,applyMiddleware 會進行 applyMiddleware(thunk, logger)(createStore)(reducer, preloadedState)
的調用。
applyMiddleware 源碼如下
export default function applyMiddleware(...middlewares) {return createStore => (...args) => {const store = createStore(...args)let dispatch = store.dispatchlet chain = []const middlewareAPI = {getState: store.getState, // 調用 redux 原生方法,獲取狀態dispatch: (...args) => dispatch(...args) // 調用 redux 原生 dispatch 方法}// 串行 middlewarechain = 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技術棧