Redux的基礎(chǔ)知識(shí)前幾篇該介紹的都介紹完了臼闻,現(xiàn)在介紹點(diǎn)高階的內(nèi)容。本篇介紹一下中間件。我最早接觸到中間件是在學(xué)習(xí)Express框架時(shí)所宰,可見(jiàn)中間件Middleware并不是個(gè)什么新事物。中間件要滿足兩個(gè)特性:一是擴(kuò)展功能畜挥,二是可以被鏈?zhǔn)浇M合仔粥。
我們來(lái)看個(gè)例子,例如Redux里dispatch一個(gè)Action,Reducer收到Action后更新state躯泰。我們希望能在這個(gè)過(guò)程中自動(dòng)打印出Action對(duì)象和更新后的state谭羔,便于調(diào)試和追蹤數(shù)據(jù)變化流。現(xiàn)在我們來(lái)寫(xiě)一個(gè)logger的中間件麦向。源碼已上傳Github瘟裸,請(qǐng)參照src/reactReduxMiddleware文件夾。
首先考慮在什么時(shí)候打印log诵竭?回顧前幾篇介紹的Redux基礎(chǔ)知識(shí)话告,Action是個(gè)plan object沒(méi)地方寫(xiě)console.log。Action creator里可以打印出Action對(duì)象秀撇,但此時(shí)還沒(méi)有執(zhí)行Reducer超棺,因此無(wú)法打印更新后的state。Reducer里可以取到Action對(duì)象和更新后的state呵燕,但它應(yīng)該是個(gè)純函數(shù)棠绘,不應(yīng)該把console.log代碼寫(xiě)進(jìn)去。最后只剩一個(gè)選擇再扭,寫(xiě)到調(diào)用dispatch的地方:
console.log('current state: ', store.getState());
console.log('dispatching', action);
store.dispatch(action);
console.log('next state', store.getState());
這樣能順利打印出log氧苍,功能實(shí)現(xiàn)了,目的達(dá)到了泛范。但顯然我們可以封裝一下让虐,在所有dispatch前后都自動(dòng)打印一下log。寫(xiě)一個(gè)增強(qiáng)版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)部仍舊調(diào)用原生的Store.dispatch,最后將Store.dispatch指向這個(gè)增強(qiáng)版区赵。在程序入口處加上這段代碼惭缰,程序員寫(xiě)業(yè)務(wù)代碼時(shí)根本意識(shí)不到這是個(gè)增強(qiáng)版dispatch方法,仿佛dispatch本就應(yīng)該打印出log一樣笼才。
這就是中間件的雛形漱受,Redux里的中間件都是改寫(xiě)dispatch方法(原因上面說(shuō)了,Redux里只有dispatch適合寫(xiě)中間件)骡送。但如果有多個(gè)中間件昂羡,都是改了dispatch方法,該怎么處理呢摔踱?
以上述打印log為例虐先,簡(jiǎn)單起見(jiàn),我們將其拆成兩個(gè)派敷。一個(gè)中間件只打印出Action赴穗,另一個(gè)中間件只打印出更新后的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,效果如圖:
打上兩個(gè)補(bǔ)丁的最終Store.dispatch如圖膀息,像個(gè)洋蔥圈:
// 只打印出 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ù)项玛,其實(shí)也是自解釋代碼。原理就是將每個(gè)中間件設(shè)計(jì)成接受一個(gè)dispatch參數(shù)弱判,并返回加工過(guò)的dispatch作為下一個(gè)中間件的參數(shù)襟沮,以方便鏈?zhǔn)秸{(diào)用。在applyMiddleware中返回的是原生store的一個(gè)副本昌腰,副本里的dispatch被最終生成的洋蔥圈式的dispatch替換了开伏。
這個(gè)applyMiddleware已經(jīng)很接近Redux官方提供的同名方法了,只是官方版更加優(yōu)化遭商,將第一個(gè)參數(shù)store也封裝掉固灵,返回的是一個(gè)createStore方法。參照l(shuí)ib/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);
上面這個(gè)版本的applyMiddleware和官方版的源碼相似度已經(jīng)達(dá)到90%劫流,只是寫(xiě)法細(xì)節(jié)上稍有不同巫玻。
最后總結(jié)一下,中間件是一個(gè)強(qiáng)化源碼功能祠汇,支持多個(gè)中間件鏈?zhǔn)秸{(diào)用的概念仍秤。在Redux里中間件等同于修改Store.dispatch方法。多個(gè)中間件用applyMiddleware方法組合成一個(gè)洋蔥圈式的強(qiáng)化版Store.dispatch座哩。常用的中間件有redux-logger徒扶,及下一篇異步Action中會(huì)介紹到的redux-thunk。