Redux中間件源碼理解

由於近期在瞭解RN的框架,學習了Redux框架,在不瞭解中間件的前提下,Redux還是很好理解的。但是由於仍然對ES6的語法掌握的不熟練,導致對中間件的理解很慢,特此整理記錄一下。

1、先看一下中間件的使用

//引用中間件
import {createStore,combineReducers,applyMiddleware} from 'redux';
import {createLogger} from 'redux-logger';
//創建一個store出來
this.reduxStore = createStore(reducer,applyMiddleware(logger));

OK,代碼很簡單,這裏我們開始準備入手源碼,來看一下中間件到底是如何起作用的。

2、store的創建

export default function createStore(reducer, preloadedState, enhancer) {
//這裏判斷了一下是否爲兩個參數的情況,就是上面代碼給出的樣例,如果在初始化state的哪裏是一個方法,同時enhancer沒定義,那麼爲兩個參數的情況,於是將初始化state和enhancer參數進行互換
  if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
    enhancer = preloadedState
    preloadedState = undefined
  }
//這裏校驗了一下enhancer必須是個function,否則就扔出去
  if (typeof enhancer !== 'undefined') {
    if (typeof enhancer !== 'function') {
      throw new Error('Expected the enhancer to be a function.')
    }
//OK,重點來了,這裏實際上調用的是applyMiddleware(logger)(createStore)(reducer, preloadedState);
    return enhancer(createStore)(reducer, preloadedState)
    ......//後面代碼就不看了
  }

OK,上面的代碼比較簡單,就是對createStore進行了參數檢測,因爲這個構造可能爲兩個參數,也可能爲三個參數,這裏進行了一次參數的校準。然後進行了關鍵代碼的調用enhancer(createStore)(reducer, preloadedState),這裏enhancer實際上就是applyMiddleware(logger),那麼我們再看一下applyMiddleware到底做了什麼

3、核心代碼applyMiddleware
這裏的源碼,大家可以去node_modules/redux/src/applyMiddleware.js中進行查看

export default function applyMiddleware(...middlewares) {
//這裏實際上就是一個組合函數的調用f(g(k(x))),其實就是下個代碼片段所顯示的東西
  return (createStore) => (reducer, preloadedState, enhancer) => {
  //生成一個store
    var store = createStore(reducer, preloadedState, enhancer)
    //獲取該store的dispatch方法
    var dispatch = store.dispatch
    //準備一個存放dispatch方法的方法鏈(實際上就是個數組)
    var chain = []
    //這個是middleware需要的參數,就是個store
    var middlewareAPI = {
      getState: store.getState,
      dispatch: (action) => dispatch(action)
    }
    //核心代碼1:對所有的中間件進行一次轉換,轉換成一組dispatch
    chain = middlewares.map(middleware => middleware(middlewareAPI))
    //核心代碼2:對轉換成的一組dispatch進行重組,組合成一個方法(PS:由於對ES6語法不熟,這裏浪費了好長時間)
    dispatch = compose(...chain)(store.dispatch)

    return {
      ...store,
      dispatch
    }
  }
}
export default function applyMiddleware(...middlewares) {
  return function (createStore) {
   return function (reducer, preloadedState, enhancer) {
    var store = createStore(reducer, preloadedState, enhancer)
    ......
    return {...store,dispatch};
  }
}
//所以纔可以這樣調用applyMiddleware(middlewares)(createStore)(reducer, preloadedState)

代碼異常的簡潔。。。。。。。。。(PS:吐槽一下,菜雞表示剛開始根本看不懂,這個中間件整整理解了一天才終於理解了點皮毛…….)

OK,接下來我們一行一行代碼仔細分析:

//這三行代碼不贅述,就是生成了一個store,然後拿到了這個store的dispatch方法,然後準備了一個dispatch鏈(就是個數組,先不用管裏面放了什麼東西)
var store = createStore(reducer, preloadedState, enhancer)
    var dispatch = store.dispatch
    var chain = []

這裏先看一下middleware(middlewareAPI)這個方法

export interface Middleware {
  <S>(api: MiddlewareAPI<S>): (next: Dispatch<S>) => Dispatch<S>;
}

這個是在redux源碼中找到的聲明,可以看到,middleware接收一個middlewareAPI這樣一個參數,返回一個入參爲dispatch,返回值爲dispatch的一個function,解釋完這個之後看下面一個代碼塊

var middlewareAPI = {
      getState: store.getState,
      dispatch: (action) => dispatch(action)
    }
    chain = middlewares.map(middleware => middleware(middlewareAPI))

這裏對所有的中間件進行了一次map操作,操作之後,chain數組中,放置了中間件所對應的入參爲dispatch,返回值爲dispatch的function。OK,繼續看下面一個代碼塊。

dispatch = compose(...chain)(store.dispatch)//這是又一行核心代碼,先看下面的代碼塊,爲compose的源碼
export default function compose(...funcs) {
  if (funcs.length === 0) {
    return arg => arg
  }

  if (funcs.length === 1) {
    return funcs[0]
  }
//上面的代碼都不管
//獲取funcs數組的最後一個方法
  const last = funcs[funcs.length - 1]
  //這裏將funcs數組從頭到倒數第二個重新放到了一個新的數組裏,倒數第一個已經在上面被拿出來了
  const rest = funcs.slice(0, -1)
  //點睛之筆來了:
  //這裏對rest數組進行了一次從右到左的數組包裹:
  //該函數在源碼中的註釋中給了一個簡潔的表達:compose(f, g, h) is identical to doing * (...args) => f(g(h(...args))).
  return (...args) => rest.reduceRight((composed, f) => f(composed), last(...args))
}

PS:代碼依舊十分的幹練………..
這裏對(…args) => rest.reduceRight((composed, f) => f(composed), last(…args))進行詳細的解釋(由於ES6語法不熟,這裏墨跡了很久)

首先看一下…args,這個並非指funcs這個形參,而是調用這個方法時傳入的實參。注意看compose方法的調用:compose(…chain)(store.dispatch),…chain對應的是funcs,而store.dispatch則對應的是…args。
然後再看一下rest.reduceRight(function(pre,current,index,array),start),這個方法。

解釋下兩個參數
1、function爲方法回調,回調回來的4個參數分別爲:
pre:上一次執行完畢之後返回的結果;
current:當前要用的數組中的數據;
index:當前數組的數據的下標;
array:當前的數組。
2、第一次執行是pre的值

OK,這時我們看一下源碼,pre對應的值爲last(…args),實際上就是chain這個dispatch鏈中的最後一個,也就是中間件的最後一個的diapatch,它調用了真正最原始的store.dispatch。即last(store.dispatch),由於chain鏈中的dispatch返回的仍然是個dispatch,所以第二次回調的pre又是一個dispatch,是last(store.dispatch)返回的結果。於是形成了一個組合函數鏈式調用:
middleware1Dispatch(middleware2Dispatch(middleware3Dispatch(store.dispatch())));
這樣就形成一個dispatch,然後再調用的時候,可以保證從外到內一次調用,然後dispatch(action)的時候,又內至外又依次進行。
這裏盜圖一張:
原文
這裏寫圖片描述

最後再看一下簡單的redux-logger的中間件的代碼:

const logger = store => next => action => {
    console.log('dispatch:',action);
    next(action);
    console.log('finish:',action);
}

由於最終的一個dispatch是一個鏈式的dispatch的組合函數,所以會又外至內的依次調用next(action)之前的代碼和next(action)代碼,當調用到最終的store.dispatch之後,會依次又內至外的調用next(action)之後的代碼,由此形成了上方那個圖,先由外至內,再由內至外。

OK,這裏只是粗淺的解釋了一下Redux中間件源碼,對於其中高深的理解並未解釋,比如裏面用的組合函數、柯里化。這些巧妙的用法和幹練的寫法需要繼續對ES6深入瞭解之後纔可以陸續的理解。

最後放一下參考的一系列文章:

精益 React 學習指南 (Lean React)- 3.3 理解 redux 中間件

深入理解redux中間件
redux middleware 詳解
淺談redux 中間件的原理
(譯)深入淺出Redux中間件
JS中的柯里化(currying)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章