摘自:please call me HRweb
redux middleware 是 redux 的一個 advanced feature. 這個概念並非很新奇,覺得在 Koa 裏面早已經實現過了. 對比與原生的redux middleware , koa 的 middleware 差很少至關因而爸爸級的 level 了. 這麼說,是有依據的. 咱們這裏,來深刻一下源碼,具體看一下redux middleware 到底作了些啥.
咱們首先來探討一下基本的源碼吧.編程
redux 的源碼能夠說是比較簡單的。 首先,入口文件是 index.js 。咱們來看一下,裏面具體的內容:redux
// 從不一樣的文件裏面導出相關方法 export { createStore, combineReducers, bindActionCreators, applyMiddleware, compose }
這裏,實際上是 redux 的提供的全部方法了. createStore,combineReducers,bindActionCreators 這三個方法,與 middleware 關係不大,我這裏就不贅述了. 這裏,主要講解一下 applyMiddleware 方法和 compose 方法.
in fact, compose 是一個很是基礎的方法, 用來以函數式的編程來組合中間件, 在 koa 中咱們也一樣碰見過這樣的寫法. applyMiddleware 也是用到這樣的方法的. so, 咱們來具體看看.app
compose 的源碼就是一個函數 compose :koa
export default function compose(...funcs) { if (funcs.length === 0) { return arg => arg } if (funcs.length === 1) { return funcs[0] } // 獲取最後一個函數 const last = funcs[funcs.length - 1]; // 獲取除最後一個之外的函數[0,length-1) const rest = funcs.slice(0, -1) // 經過函數 curry 化 return (...args) => rest.reduceRight((composed, f) => f(composed), last(...args)) }
第一眼看的時候, 確定超級S B。。。 md... 這寫個啥... 看了一下官方給的註釋就是:async
// 簡單 demo 就是 compose(f, g, h) is identical to doing (...args) => f(g(h(...args))).
合着就是個函數嵌套的寫法。。。
關鍵坑爹的在於他的reduceRight方法, 寫的真心6. 因爲,return 兩個函數時,只會返回第二個執行的結果:ide
function test(a,b){ return a(),b(); } console.log(test(a=>1,b=>2)); // 開玩笑的. 上面那種只是科普一下. 他真正的機制其實是利用 reduceRight 的第二個參數來執行的 Array.reduceRight(fn,start); // 主要仍是這裏的start, 至關於就是 last(...args) // 將上面代碼翻譯一下就是 rest.reduceRight(function(composed, f){return f(composed)}, last(...args)); //... 慢慢去體會吧...
因此, 一開始看的時候,在糾結 最後一個 composed 都沒執行... 後來發現, 原來還有一層 last(...args).
不過實話說, 真心沒有 koa 裏面的 compose 函數寫得好, 你直接先寫一個 noop 函數不行嗎!!!函數
// 俺 實際寫了一個替換的compose. 感受這個看的清楚一點 function compose(...funcs) { return function(...args) { var len = funcs.length, middle = funcs[--len](...args); while (len--) { middle = funcs[len].call(this, middle); } return middle; } } // 測試 console.log(compose(a => a, b => b, (c,b) => c+b)('a', 'b'));
這個是簡單的compose 函數. 下面,咱們來看一下重點,關於 redux-middleware 的核心方法, applyMiddleware.oop
因爲這個中間件有點複雜, 對傳入的函數有具體的要求. 咱們先來看一下使用該方法的上下文:
直接看 offical website 找到一個 demo:測試
let store = createStore( todoApp, // applyMiddleware() tells createStore() how to handle middleware applyMiddleware(logger, crashReporter) )
最終 applyMiddleware return 的結果,還須要返回到 createStore 裏去的. 經過 createStore 傳入方法時, 函數裏面並未對 裏面作什麼處理.
function createStore(reducer, preloadedState, enhancer) { // 這裏就是一些列條件判斷, 若是你使用 middle 是上面的形式,那麼就會直接將參數賦給 enhancer if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') { enhancer = preloadedState preloadedState = undefined } if (typeof enhancer !== 'undefined') { if (typeof enhancer !== 'function') { throw new Error('Expected the enhancer to be a function.') } // 實際上調用 applyMiddleware 方法的地方. 注意他傳入的參數便可. z return enhancer(createStore)(reducer, preloadedState) } }
ok, 咱們瞭解了傳入 applyMiddleware 的參數後, 繼續看. 中間件的寫法:
// 這裏就看一下logger 就Ok const logger = store => next => action => { // debug info console.group(action.type) console.info('dispatching', action) let result = next(action) // debug info console.log('next state', store.getState()) console.groupEnd(action.type) return result } // 咱們將 debug 信息去掉以後,能夠獲得一個精簡版的 middleware const logger = store => next => action => { // 傳遞前, 執行的代碼 let result = next(action) // 傳遞完, 執行的代碼 return result }
看到這裏,有種 koa 的感受. next(action) 顯示的將控制權交給下一個函數進行執行. 至關於就是 onion model.
這裏, 放一下 applyMiddleware 的源碼:
export default function applyMiddleware(...middlewares) { return (createStore) => (reducer, preloadedState, enhancer) => { var store = createStore(reducer, preloadedState, enhancer) var dispatch = store.dispatch var chain = [] var middlewareAPI = { getState: store.getState, dispatch: (action) => dispatch(action) } chain = middlewares.map(middleware => middleware(middlewareAPI)) dispatch = compose(...chain)(store.dispatch) return { ...store, dispatch } } }
看到這裏, 我也是有點驚訝, 第一是他的中間件傳遞的複雜度巧妙的轉化爲箭頭函數的寫法, 第二是他顯示的改變了真正的dispatch的內容。
最後實際調用整個流程,是直接根據applyMiddleware提供的方法來的:
// 注意這裏是 applyMiddleware 提供的 dispatch 方法 store.dispatch(action)
若是按照上面的調用方式寫的話,具體調用順序就是:
applyMiddleware(logger, crashReporter)
applyMiddleware 整個執行過程:
對應於上文, 整個API的流程圖爲:
關鍵點在於applyMiddleware 和 中間件兩個內容.
關於 redux-middleware 還有一個比較流行的庫, 即, redux-thunk . 該庫灰常簡單, 就一個函數.
直接看源碼算了:
function createThunkMiddleware(extraArgument) { return ({ dispatch, getState }) => next => action => { if (typeof action === 'function') { return action(dispatch, getState, extraArgument); } return next(action); }; } const thunk = createThunkMiddleware(); thunk.withExtraArgument = createThunkMiddleware; export default thunk;
他和原來的中間件的寫法有一個很是不一樣的地方,在於. 他寫中間件的地方, 不在 createStore 裏面, 而在 dispatch 裏面.
// 初始化調用 const store = createStore( rootReducer, applyMiddleware(thunk) ); // thunk 類型的中間件 function doSth(forPerson) { // 這裏必須返回一個函數... 才能達到中間件的效果 return function (dispatch) { return async().then( sauce => dispatch(makeASandwich(forPerson, sauce)), error => dispatch(apologize('The Sandwich Shop', forPerson, error)) ); }; } // 更簡便的寫法能夠爲: let doSth = forPerson=>dispatch=> async().then( sauce => dispatch(makeASandwich(forPerson, sauce)), error => dispatch(apologize('The Sandwich Shop', forPerson, error)) )
看源碼,咱們能夠很容易發現, 在實際調用 dispatch 時, 不只僅只有 dispatch 這一個參數,還有 getState,extraArgument 這兩個參數。 so, what are they doing?
getState 這個就不用說了, 就是用來獲取當前 redux 的 state.
那 extraArgument 幹啥嘞?
看源碼很容易發現, 就是在初始化 thunk 時, 傳入的參數. 其實, 也不會常常用到. 因此, 咱們中間件實際能夠寫爲:
let doSth = forPerson=>(dispatch,getState,extArgs)=> async().then( sauce => dispatch(makeASandwich(forPerson, sauce)), error => dispatch(apologize('The Sandwich Shop', forPerson, error)) )
對比與上面的 applyMiddle 來講, 使用 redux-thunk 能夠實現 私有, 定製化的 中間件操做. 而,原來的 applyMiddleware 則 只能在初始階段設置相關的中間件, 但 卻能夠實現 next 的執行域的分離. 因此, 兩個均可以使用, 只是看你具體需求是啥.