因爲一直用業界封裝好的如redux-logger、redux-thunk
此類的中間件,並無深刻去了解過redux
中間件的實現方式。正好前些時間有個需求須要對action
執行時作一些封裝,因而藉此瞭解了下Redux Middleware
的原理。
首先簡單提下什麼是中間件,該部分與下文關係不大,能夠跳過。來看眼這個經典的圖。react
不難發現:redux
middleware
時,在dispatch(action)
時會執行rootReducer
,並根據action
的type
更新返回相應的state
。middleware
時,簡言之,middleware
會將咱們當前的action
作相應的處理,隨後再交付rootReducer
執行。 好比現有一個action
以下:segmentfault
function getData() { return { api: '/cgi/getData', type: [GET_DATA, GET_DATA_SUCCESS, GET_DATA_FAIL] } }
咱們但願執行該action
時能夠發起相應請求,而且根據請求結果由定義的type
匹配到相應的reducer
,那麼能夠自定義方法處理該action
,所以該方法封裝成中間件以前多是這樣的:api
async function dispatchPre(action, dispatch) { const api = action.api; const [ fetching_type, success_type, fail_type] = action.type; // 拉取數據 const res = await request(api); // 拉取時狀態 dispatch({type: fetching_type}); // 成功時狀態 if (res.success) { dispatch({type: success_type, data: res.data}); console.log('GET_SUCCESS'); } // 失敗時狀態 if (res.fail) { dispatch({type: fail_type}); console.log('GET_FAIL'); }; } // 調用: dispatchPre(action(), dispatch)
那如何封裝成中間件,讓咱們在能夠直接在dispatch(action)
時就作到這樣呢?可能會首先想到改變dispatch
指向數組
// 儲存原來的dispatch const dispatch = store.dispatch; // 改變dispatch指向 store.dispatch = dispatchPre; // 重命名 const next = dispatch;
截止到這咱們已經瞭解了中間件的基本原理了~閉包
瞭解了基本原理能有助於咱們更快地讀懂middleware
的源碼。
業務中,通常咱們會這樣添加中間件並使用。app
createStore(rootReducer, applyMiddleware.apply(null, [...middlewares]))
接下來咱們能夠重點關注這兩個函數createStore
、applyMiddleware
async
// 摘至createStore export function createStore(reducer, rootState, enhance) { ... if (typeof enhancer !== 'undefined') { if (typeof enhancer !== 'function') { throw new Error('Expected the enhancer to be a function.') } /* 若使用中間件,這裏 enhancer 即爲 applyMiddleware() 如有enhance,直接返回一個加強的createStore方法,能夠類比成react的高階函數 */ return enhancer(createStore)(reducer, preloadedState) } ... }
再看看applyMiddleware
作了什麼,applyMiddleware
函數很是簡單,就十來行代碼,這裏將其完整複製出來。函數
export default function applyMiddleware(...middlewares) { return createStore => (...args) => { const store = createStore(...args) let dispatch = () => { throw new Error( `Dispatching while constructing your middleware is not allowed. ` + `Other middleware would not be applied to this dispatch.` ) } const middlewareAPI = { getState: store.getState, dispatch: (...args) => dispatch(...args) } // 一、將store對象的基本方法傳遞給中間件並依次調用中間件 const chain = middlewares.map(middleware => middleware(middlewareAPI)) // 二、改變dispatch指向,並將最初的dispatch傳遞給compose dispatch = compose(...chain)(store.dispatch) return { ...store, dispatch } } }
根據源碼,咱們能夠將其主要功能按步驟劃分以下:源碼分析
一、依次執行middleware
。
將middleware
執行後返回的函數合併到一個chain
數組,這裏咱們有必要看看標準middleware
的定義格式,以下
export default store => next => action => {} // 即 function (store) { return function(next) { return function (action) { return {} } } }
那麼此時合併的chain
結構以下
[ ..., function(next) { return function (action) { return {} } } ]
二、改變dispatch
指向。
想必你也注意到了compose
函數,compose
函數以下:[...chain].reduce((a, b) => (...args) => a(b(...args)))
實際就是一個柯里化函數,即將全部的middleware
合併成一個middleware
,並在最後一個middleware
中傳入當前的dispatch
。
*compose
可能會看得有點蒙,不理解柯里化函數的同窗能夠跳到一個例子讀懂compose先了解下。
// 假設chain以下: chain = [ a: next => action => { console.log('第1層中間件') return next(action) } b: next => action => { console.log('第2層中間件') return next(action) } c: next => action => { console.log('根dispatch') return next(action) } ]
調用compose(...chain)(store.dispatch)
後返回a(b(c(dispatch)))
。
能夠發現已經將全部middleware
串聯起來了,並同時修改了dispatch
的指向。
最後看一下這時候compose執行返回,以下
dispatch = a(b(c(dispatch))) // 調用dispatch(action) // 執行循序 /* 1. 調用 a(b(c(dispatch)))(action) __print__: 第1層中間件 2. 返回 a: next(action) 即b(c(dispatch))(action) 3. 調用 b(c(dispatch))(action) __print__: 第2層中間件 4. 返回 b: next(action) 即c(dispatch)(action) 5. 調用 c(dispatch)(action) __print__: 根dispatch 6. 返回 c: next(action) 即dispatch(action) 7. 調用 dispatch(action) */
上文提到compose是個柯里化函數,能夠當作是將全部函數合併成一個函數並返回的函數。
例如先定義3個方法
function A(x){ return x + 'a' } function B(y){ return y + 'b' } function C(){ return 'c' } var d = [...A, b, C].reduce((a, b) => (d) => {console.log(d, a, b); a(b(d))}) d // 打印d // f (d) { console.log(d, a, b); return a(b(d)) } d('d') // 調用d /* * d * f(d) { console.log(d, a, b); return a(b(d)) } * f C() { return 'c' } */ /* * c * f A(x) { return x + 'a' } * f B(y) { return y + 'b' } */
不難發現,使用閉包,在調用d
的時候,將a
、b
函數儲存在了內存中,調用時會依次將數組從右至左的函數返回作爲參數傳遞給下一個函數使用