深入理解redux中間件

vb672616 8年前發布 | 10K 次閱讀 中間件 Redux JavaScript開發

redux middleware 是 redux 的一個 advanced feature. 這個概念并不是很新奇,以為在 Koa 里面早已經實現過了. 對比與原生的redux middleware , koa 的 middleware 差不多相當于是爸爸級的 level 了. 這么說,是有依據的. 我們這里,來深入一下源碼,具體看一下redux middleware 到底做了些啥.

我們首先來探討一下基本的源碼吧.

redux 的源碼可以說是比較簡單的。 首先,入口文件是 index.js 。我們來看一下,里面具體的內容:

// 從不同的文件里面導出相關方法
export {
  createStore,
  combineReducers,
  bindActionCreators,
  applyMiddleware,
  compose
}

這里,其實是 redux 的提供的所有方法了. createStore,combineReducers,bindActionCreators 這三個方法,與 middleware 關系不大,我這里就不贅述了. 這里,主要講解一下 applyMiddleware 方法和 compose 方法.

in fact, compose 是一個非常基礎的方法, 用來以函數式的編程來組合中間件, 在 koa 中我們也同樣遇見過這樣的寫法. applyMiddleware 也是用到這樣的方法的. so, 我們來具體看看.

compose 方法

compose 的源碼就是一個函數 compose :

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... 這寫個啥... 看了一下官方給的注釋就是:

// 簡單 demo 就是
compose(f, g, h) is identical to doing (...args) => f(g(h(...args))).

合著就是個函數嵌套的寫法。。。

關鍵坑爹的在于他的reduceRight方法, 寫的真心6. 由于,return 兩個函數時,只會返回第二個執行的結果:

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.

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)

applyMiddleware all procedure

applyMiddleware 整個執行過程:

對應于上文, 整個API的流程圖為:

關鍵點在于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))
)

不過,怎樣用, 還是看自己吧. 本人比較傾向于使用 原來的 applyMiddleware. 因為, 那個已經滿足我的需求了.

 

來自:https://segmentfault.com/a/1190000006876228

 

 本文由用戶 vb672616 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!