Redux介紹之中間件

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è)洋蔥圈:


至此我們已經(jīng)知道中間件的用途了般眉,但調(diào)用起來(lái)比較麻煩,如果有5個(gè)中間件要調(diào)用5次太麻煩了潜支〉樵撸可以設(shè)計(jì)的更智能點(diǎn),我們自定義一個(gè)applyMiddleware方法(applyMiddleware其實(shí)是Redux為中間件提供的官方方法冗酿,現(xiàn)在我們自己來(lái)實(shí)現(xiàn)這個(gè)方法)埠对,允許將所有中間件以數(shù)組形式傳遞進(jìn)去,參照l(shuí)ib/middleware.js的Step3:
// 只打印出 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。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末根穷,一起剝皮案震驚了整個(gè)濱河市姜骡,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌屿良,老刑警劉巖圈澈,帶你破解...
    沈念sama閱讀 216,496評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異尘惧,居然都是意外死亡康栈,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,407評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)啥么,“玉大人登舞,你說(shuō)我怎么就攤上這事⌒伲” “怎么了菠秒?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,632評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)氯迂。 經(jīng)常有香客問(wèn)我践叠,道長(zhǎng),這世上最難降的妖魔是什么嚼蚀? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,180評(píng)論 1 292
  • 正文 為了忘掉前任禁灼,我火速辦了婚禮,結(jié)果婚禮上轿曙,老公的妹妹穿的比我還像新娘弄捕。我一直安慰自己,他們只是感情好拳芙,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,198評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布察藐。 她就那樣靜靜地躺著,像睡著了一般舟扎。 火紅的嫁衣襯著肌膚如雪分飞。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,165評(píng)論 1 299
  • 那天睹限,我揣著相機(jī)與錄音譬猫,去河邊找鬼。 笑死羡疗,一個(gè)胖子當(dāng)著我的面吹牛染服,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播叨恨,決...
    沈念sama閱讀 40,052評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼柳刮,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了痒钝?” 一聲冷哼從身側(cè)響起秉颗,我...
    開(kāi)封第一講書(shū)人閱讀 38,910評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎送矩,沒(méi)想到半個(gè)月后蚕甥,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,324評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡栋荸,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,542評(píng)論 2 332
  • 正文 我和宋清朗相戀三年菇怀,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了凭舶。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,711評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡爱沟,死狀恐怖帅霜,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情钥顽,我是刑警寧澤义屏,帶...
    沈念sama閱讀 35,424評(píng)論 5 343
  • 正文 年R本政府宣布,位于F島的核電站蜂大,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏蝶怔。R本人自食惡果不足惜奶浦,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,017評(píng)論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望踢星。 院中可真熱鬧澳叉,春花似錦、人聲如沸沐悦。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,668評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)藏否。三九已至瓶殃,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間副签,已是汗流浹背遥椿。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,823評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留淆储,地道東北人冠场。 一個(gè)月前我還...
    沈念sama閱讀 47,722評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像本砰,于是被迫代替她去往敵國(guó)和親碴裙。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,611評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容