Redux介紹之中間件

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如圖,像個洋蔥圈:


至此我們已經(jīng)知道中間件的用途了,但調用起來比較麻煩,如果有5個中間件要調用5次太麻煩了。可以設計的更智能點,我們自定義一個applyMiddleware方法(applyMiddleware其實是Redux為中間件提供的官方方法,現(xiàn)在我們自己來實現(xiàn)這個方法),允許將所有中間件以數(shù)組形式傳遞進去,參照lib/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ù),其實也是自解釋代碼。原理就是將每個中間件設計成接受一個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。

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

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