Redux的基礎知識前幾篇該介紹的都介紹完了,現(xiàn)在介紹點高階的內(nèi)容。本篇介紹一下中間件。我最早接觸到中間件是在學習Express框架時,可見中間件Middleware并不是個什么新事物。中間件要滿足兩個特性:一是擴展功能,二是可以被鏈式組合。
我們來看個例子,例如Redux里dispatch一個Action,Reducer收到Action后更新state。我們希望能在這個過程中自動打印出Action對象和更新后的state,便于調試和追蹤數(shù)據(jù)變化流。現(xiàn)在我們來寫一個logger的中間件。源碼已上傳Github,請參照src/reactReduxMiddleware文件夾。
首先考慮在什么時候打印log?回顧前幾篇介紹的Redux基礎知識,Action是個plan object沒地方寫console.log。Action creator里可以打印出Action對象,但此時還沒有執(zhí)行Reducer,因此無法打印更新后的state。Reducer里可以取到Action對象和更新后的state,但它應該是個純函數(shù),不應該把console.log代碼寫進去。最后只剩一個選擇,寫到調用dispatch的地方:
console.log('current state: ', store.getState());
console.log('dispatching', action);
store.dispatch(action);
console.log('next state', store.getState());
這樣能順利打印出log,功能實現(xiàn)了,目的達到了。但顯然我們可以封裝一下,在所有dispatch前后都自動打印一下log。寫一個增強版Store.dispatch:
const preDispatch = store.dispatch;
store.dispatch = (action) => {
console.log('current state: ', store.getState());
console.log('action: ', action);
preDispatch(action);
console.log('next state: ', store.getState());
};
上面都是自解釋代碼,函數(shù)簽名與返回值和原生Store.dispatch一模一樣,函數(shù)內(nèi)部仍舊調用原生的Store.dispatch,最后將Store.dispatch指向這個增強版。在程序入口處加上這段代碼,程序員寫業(yè)務代碼時根本意識不到這是個增強版dispatch方法,仿佛dispatch本就應該打印出log一樣。
這就是中間件的雛形,Redux里的中間件都是改寫dispatch方法(原因上面說了,Redux里只有dispatch適合寫中間件)。但如果有多個中間件,都是改了dispatch方法,該怎么處理呢?
以上述打印log為例,簡單起見,我們將其拆成兩個。一個中間件只打印出Action,另一個中間件只打印出更新后的state:
// 只打印出 Action
export const loggerAction = (store) => {
const preDispatch = store.dispatch;
store.dispatch = (action) => {
console.log('dispatching', action);
const result = preDispatch(action);
return result;
};
};
// 只打印出 更新后的state
export const loggerState = (store) => {
const preDispatch = store.dispatch;
store.dispatch = (action) => {
const result = preDispatch(action);
console.log('next state', store.getState());
return result;
};
};
const store = createStore(reducer);
loggerAction(store);
loggerState(store);
順利打出log,效果如圖:
打上兩個補丁的最終Store.dispatch如圖,像個洋蔥圈:
// 只打印出 Action
export const loggerAction = (store) => (dispatch) => (action) => {
console.log('dispatching', action);
const result = dispatch(action);
return result;
};
// 只打印出 更新后的state
export const loggerState = (store) => (dispatch) => (action) => {
const result = dispatch(action);
console.log('next state', store.getState());
return result;
};
export const applyMiddleware = (store, middlewares) => {
let dispatch = store.dispatch;
middlewares.forEach((middleware) => {
dispatch = middleware(store)(dispatch);
});
return {
...store,
dispatch,
};
};
entries/reactReduxMiddleware.js:
let store = createStore(reducer);
store = applyMiddleware(store, [loggerAction, loggerState]);
分析一下上面的代碼,如果你熟悉ES6的箭頭函數(shù),其實也是自解釋代碼。原理就是將每個中間件設計成接受一個dispatch參數(shù),并返回加工過的dispatch作為下一個中間件的參數(shù),以方便鏈式調用。在applyMiddleware中返回的是原生store的一個副本,副本里的dispatch被最終生成的洋蔥圈式的dispatch替換了。
這個applyMiddleware已經(jīng)很接近Redux官方提供的同名方法了,只是官方版更加優(yōu)化,將第一個參數(shù)store也封裝掉,返回的是一個createStore方法。參照lib/middleware.js的final Step:
export const applyMiddleware = (...middlewares) => {
return (createStore) => (reducer, preloadedState, enhancer) => {
const store = createStore(reducer, preloadedState, enhancer);
let dispatch = store.dispatch;
middlewares.forEach((middleware) => {
dispatch = middleware(store)(dispatch);
});
return {
...store,
dispatch,
};
};
};
entries/reactReduxMiddleware.js:
const createStoreWithMiddleware = applyMiddleware(loggerAction, loggerState)(createStore);
const store = createStoreWithMiddleware(reducer);
上面這個版本的applyMiddleware和官方版的源碼相似度已經(jīng)達到90%,只是寫法細節(jié)上稍有不同。
最后總結一下,中間件是一個強化源碼功能,支持多個中間件鏈式調用的概念。在Redux里中間件等同于修改Store.dispatch方法。多個中間件用applyMiddleware方法組合成一個洋蔥圈式的強化版Store.dispatch。常用的中間件有redux-logger,及下一篇異步Action中會介紹到的redux-thunk。