Redux中間件源碼剖析

redux中間件

redux 是一個輕量級的數據流管理工具,主要解決了 component -> action -> reducer -> state 的單向數據流轉問題。同時, redux 也提供了相似於 koa 和 express 的中間件(middleware)的概念,讓咱們能夠介入數據從 actionreducer 之間的傳遞過程,從而改變數據流,實現如異步、數據過濾、日誌上報等功能。javascript

redux 的中間件是經過第三方插件的方式實現,自己源碼也不是不少,咱們就從源碼來解讀 redux 的中間件機制。java

首先來看咱們是如何加載一箇中間件的,以 redux-thunk 爲例:react

import { createStore, applyMiddleware } from 'redux';
import { Provider } from 'react-redux';
import thunk from 'redux-thunk';
import reducers from './reducers.js';

let store = createStore(
    reducers,
    preloadedState,
    applyMiddleware(thunk)
);
// ...

加載中間件有兩個核心的方法: createStoreapplyMiddleware ,接下來咱們就從源碼剖析,看 redux 中間件的運行原理究竟是怎麼樣的。express

applyMiddleware

首先看一下 applyMiddleware 的源碼:redux

import compose from './compose'

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
    }
  }
}

這就是 applyMiddleware 方法的所有內容,咱們細剖來看。首先, applyMiddleware 方法接收一個或多箇中間件做爲參數(會被函數做爲ES6的 rest 參數讀取,變成一個數組),而後返回了一個匿名函數:數組

return (createStore) => (reducer, preloadedState, enhancer) => {
    ...
}

這種寫法一樣是 ES6 的寫法,翻譯成 ES5 其實就是:app

return function (createStore) {
    return function (reducer, preloadedState, enhancer) {
        ...
    }
};

也就是說,負責加載中間件的 applyMiddleware 方法其實只是返回了一個帶有一個入參的匿名函數。此時,createStore 方法執行的時候即爲:koa

let store = createStore(
    reducers,
    defaultReducer,
    function (createStore) {...} // applyMiddleware(thunk)
);

接下來就來看看 createStore 作了什麼。異步

createStore

一樣先來看一看 createStore 的源碼:ide

export default function createStore(reducer, preloadedState, 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.')
    }

    return enhancer(createStore)(reducer, preloadedState)
  }
  
  var currentReducer = reducer
  var currentState = preloadedState
  var currentListeners = []
  var nextListeners = currentListeners
  var isDispatching = false

  function ensureCanMutateNextListeners() {...}
  function getState() {return currentState;}
  function subscribe(listener) {...}
  function dispatch(action) {...}
  function replaceReducer(nextReducer) {...}
  function observable() {...}
  
  dispatch({ type: ActionTypes.INIT })

  return {
    dispatch,
    subscribe,
    getState,
    replaceReducer,
    [$$observable]: observable
  }
}

createStore 函數接收三個參數:

  • reducer :即咱們經過 combineReducers 導出的 reducer 集合;
  • preloadedState :可選參數,初始化的 state
  • enhancer :用來加強 store ,也就是經過 applyMiddleware 返回的匿名函數。

逐塊分析代碼:

if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
    enhancer = preloadedState
    preloadedState = undefined
}

這塊代碼來處理 preloadedState 可選參數,處理是兩個參數仍是三個參數的狀況,比較簡單。簡單歸納就是,若是隻傳了兩個參數,而且第二個參數爲函數,第二個參數會被看成 enhancer

繼續往下看:

if (typeof enhancer !== 'undefined') {
    if (typeof enhancer !== 'function') {
      throw new Error('Expected the enhancer to be a function.')
    }

    return enhancer(createStore)(reducer, preloadedState)
}

這塊代碼實際上是 redux 中間件的核心入口,也是有無中間件處理流程的分叉口。若是咱們註冊了中間件,就會執行 enhancer ,而若是沒有註冊的話,就直接往下執行而後返回 dispatch, getState 等等這些東西了。咱們來看註冊中間件的狀況下, enhancer 方法執行的時候發生了什麼。

enhancer 就是上面講過的 applyMiddleware 函數返回的匿名函數。 enhancer 方法接收一個參數: createStore ,你沒看錯,就是擁有 reducer, preloadedState, enhancer 這三個參數的 createStore

// applyMiddleware(thunk) 返回的匿名函數
// 接收了 enhancer 傳來的 createStore
return function (createStore) { // 第一層匿名函數
    // 接收了 enhancer(createStore) 傳來的 reducer, preloadedState
    return function (reducer, preloadedState, enhancer) { // 第二層匿名函數
        ...
    }
};

實際上,enhancer(createStore)(reducer, preloadedState) 執行的時候,參數 createStore 給了第一層匿名函數,由於咱們的目的是要對 createStore 進行修飾。而 reducerpreloadedState 兩個參數給了第二層匿名函數。

第二層匿名函數一樣擁有 reducer, preloadedState, enhancer 三個參數,也即:

// 接收了 enhancer(createStore) 傳來的 reducer, preloadedState
return function (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
    }
}

那咱們就來看一看這個匿名函數又作了什麼事情。

var store = createStore(reducer, preloadedState, enhancer)

首先,第二層匿名函數又調了 createStore 方法(又回去了…orz)。剛纔也說到,在咱們應用入口調 createStore 方法的時候,第三個參數 enhancer 其實傳的是咱們註冊的中間件。而這時,createStore 接收到的參數只有 reducerpreloadedState ,也就是說會按照正常的沒有註冊中間件的狀況,直接往下執行而後返回 dispatch, getState 等等這些東西。因此這時候 store 拿到的是:

return {
    dispatch,
    subscribe,
    getState,
    replaceReducer,
    [$$observable]: observable
}

接着往下看。

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)

別忘了,咱們目前執行的第一層匿名函數和第二層匿名函數,都是在 applyMiddleware 方法的做用域內(都是 applyMiddleware 返回的匿名函數),因此能夠直接訪問 middlewares 參數。上面 chain 的值就是對中間件進行map,也就是調用中間件的方法。咱們以 redux-thunk 爲例,看一下 redux-thunk 的源碼:

export default function thunkMiddleware({ dispatch, getState }) {
  return function(next) {
    return function(action) {
      return typeof action === 'function' ?
        action(dispatch, getState) :
        next(action);
    }
  }
}

是的, redux-thunk 源碼就這些。參數裏的 dispatch, getState 就是咱們在 map 的時候,調用 middleware 方法,傳進來的 middlewareAPI 。因此咱們知道了 chain 的值是一個數組,數組的每一項是調用每一箇中間件以後的返回函數

咱們再來看 dispatch 這一行發生了什麼。這裏有一個 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]
  const rest = funcs.slice(0, -1)
  return function (...args) {
      rest.reduceRight(function(composed, f) {
      f(composed)
    }, last(...args))
  }
}

compose 相似於 ArrayreduceRight 方法的處理方式,從數組最後一個數組依次向前處理。 若是不太熟悉,看下這個例子就會很快明白:

/**
 * [description]
 * @param  {[type]} previousValue [前一個項]
 * @param  {[type]} currentValue  [當前項]
 */
[0, 1, 2, 3, 4].reduceRight(function(previousValue, currentValue, index, array) {
  return previousValue + currentValue;
}, 10);

以10爲初始值,從數組的最後一位數字向左依次累加。因此結合上面的代碼,能夠知道 compose(...chain) 的運行結果是函數數組 chain最右邊的元素開始,帶上 store.dispatch 參數執行後依次做爲前面一個函數的參數,相似下面這樣:

A = function () {};
B = function () {};
C = function () {};

chain = [A, B, C];
//dispatch = compose(...chain)(store.dispatch)
dispatch = A(B(C(store.dispatch)))

明白了 compose 方法,咱們就假設只有一箇中間件,dispatch 的值就等於:

function(next) {
  return function(action) {
      return typeof action === 'function' ?
          action(dispatch, getState) :
          next(action);
  }
}(store.dispatch)

也就是說,其實 next 參數就等於 store.dispatch 。而此時, dispatch 就等於:

dispatch = function(action) {
    return typeof action === 'function' ?
        action(dispatch, getState) :
        next(action);
}

咱們結合 redux-thunk 的用法來分析這個中間件是如何運行的。

// 異步的 action
function incrementAsync() {
  return dispatch => {
    setTimeout(() => {
      dispatch(increment());
    }, 1000);
  };
}

觸發上面異步 action 的方式是:

dispatch(incrementAsync());

回想上面的代碼,dispatch 方法是接收一個 action 參數的函數,而這裏的 action 就是 incrementAsync() ,進入 dispatch 方法以後,就是:

return typeof action === 'function' ?
    action(dispatch, getState) :
    next(action);

action 的值爲 function 時,就調用這個 function ,而且把 dispatch, getState 傳給這個 function ,若是不是 function ,就直接 store.dispatch(action) (如上面所講,next的值就是 store.dispatch )。

那這是隻有一箇中間件的狀況,有多箇中間件時,next 就是下一個中間件,一直到調用到最後一箇中間件爲止。(腦殼已變成一鍋粥/(ㄒoㄒ)/~~)

小結

回到咱們最開始講到的,redux 的中間件其實就是讓咱們能夠介入到 actionreducer 之間的過程,咱們能夠把這個過程理解成主幹和分支的概念,redux 默認的同步數據流就是主幹,中間件就是分支,主幹和分支的分水嶺從這裏時出現:

if (typeof enhancer !== 'undefined') {
    if (typeof enhancer !== 'function') {
      throw new Error('Expected the enhancer to be a function.')
    }

    return enhancer(createStore)(reducer, preloadedState) // 進入中間件分支
}

當中間件分支處理完 store 之後,就又回到了主幹。這種方式實際上是使用了裝飾者模式,經過不一樣的中間件對 createStore 進行修飾,造成最後的新的 createStore 方法,這樣一來,經過這個方法建立的 store 就擁有了中間件的處理結果。過程的確是比較繞的,但把源碼和中間件的用法結合起來看的話,其實也就不難理解了。

相關文章
相關標籤/搜索