redux 是一個輕量級的數據流管理工具,主要解決了 component -> action -> reducer -> state 的單向數據流轉問題。同時, redux 也提供了相似於 koa 和 express 的中間件(middleware)的概念,讓咱們能夠介入數據從 action
到 reducer
之間的傳遞過程,從而改變數據流,實現如異步、數據過濾、日誌上報等功能。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) ); // ...
加載中間件有兩個核心的方法: createStore
和 applyMiddleware
,接下來咱們就從源碼剖析,看 redux 中間件的運行原理究竟是怎麼樣的。express
首先看一下 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
的源碼: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 進行修飾。而 reducer
, preloadedState
兩個參數給了第二層匿名函數。
第二層匿名函數一樣擁有 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
接收到的參數只有 reducer
和 preloadedState
,也就是說會按照正常的沒有註冊中間件的狀況,直接往下執行而後返回 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
相似於 Array
的 reduceRight
方法的處理方式,從數組最後一個數組依次向前處理。 若是不太熟悉,看下這個例子就會很快明白:
/** * [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 的中間件其實就是讓咱們能夠介入到 action
和 reducer
之間的過程,咱們能夠把這個過程理解成主幹和分支的概念,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
就擁有了中間件的處理結果。過程的確是比較繞的,但把源碼和中間件的用法結合起來看的話,其實也就不難理解了。