深刻理解redux中間件

摘自: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 的源碼就是一個函數 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

applyMiddleware 中間件

因爲這個中間件有點複雜, 對傳入的函數有具體的要求. 咱們先來看一下使用該方法的上下文:
直接看 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)

procedure

applyMiddleware all procedure

applyMiddleware 整個執行過程:

procedure

對應於上文, 整個API的流程圖爲:
關鍵點在於applyMiddleware 和 中間件兩個內容.

applyMiddleware

關於 redux-middleware 還有一個比較流行的庫, 即, redux-thunk . 該庫灰常簡單, 就一個函數.

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 的執行域的分離. 因此, 兩個均可以使用, 只是看你具體需求是啥.

相關文章
相關標籤/搜索