Redux介绍之中间件

作者: 张歆琳 | 来源:发表于2017-05-17 23:25 被阅读183次

    Redux的基础知识前几篇该介绍的都介绍完了,现在介绍点高阶的内容。本篇介绍一下中间件。我最早接触到中间件是在学习Express框架时,可见中间件Middleware并不是个什么新事物。中间件要满足两个特性:一是扩展功能,二是可以被链式组合。

    我们来看个例子,例如Redux里dispatch一个Action,Reducer收到Action后更新state。我们希望能在这个过程中自动打印出Action对象和更新后的state,便于调试和追踪数据变化流。现在我们来写一个logger的中间件。源码已上传Github,请参照src/reactReduxMiddleware文件夹。

    首先考虑在什么时候打印log?回顾前几篇介绍的Redux基础知识,Action是个plan object没地方写console.log。Action creator里可以打印出Action对象,但此时还没有执行Reducer,因此无法打印更新后的state。Reducer里可以取到Action对象和更新后的state,但它应该是个纯函数,不应该把console.log代码写进去。最后只剩一个选择,写到调用dispatch的地方:

    console.log('current state: ', store.getState());
    console.log('dispatching', action);
    store.dispatch(action);
    console.log('next state', store.getState());
    

    这样能顺利打印出log,功能实现了,目的达到了。但显然我们可以封装一下,在所有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());
    };
    

    上面都是自解释代码,函数签名与返回值和原生Store.dispatch一模一样,函数内部仍旧调用原生的Store.dispatch,最后将Store.dispatch指向这个增强版。在程序入口处加上这段代码,程序员写业务代码时根本意识不到这是个增强版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如图,像个洋葱圈:


    至此我们已经知道中间件的用途了,但调用起来比较麻烦,如果有5个中间件要调用5次太麻烦了。可以设计的更智能点,我们自定义一个applyMiddleware方法(applyMiddleware其实是Redux为中间件提供的官方方法,现在我们自己来实现这个方法),允许将所有中间件以数组形式传递进去,参照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的箭头函数,其实也是自解释代码。原理就是将每个中间件设计成接受一个dispatch参数,并返回加工过的dispatch作为下一个中间件的参数,以方便链式调用。在applyMiddleware中返回的是原生store的一个副本,副本里的dispatch被最终生成的洋葱圈式的dispatch替换了。

    这个applyMiddleware已经很接近Redux官方提供的同名方法了,只是官方版更加优化,将第一个参数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和官方版的源码相似度已经达到90%,只是写法细节上稍有不同。

    最后总结一下,中间件是一个强化源码功能,支持多个中间件链式调用的概念。在Redux里中间件等同于修改Store.dispatch方法。多个中间件用applyMiddleware方法组合成一个洋葱圈式的强化版Store.dispatch。常用的中间件有redux-logger,及下一篇异步Action中会介绍到的redux-thunk。

    相关文章

      网友评论

        本文标题:Redux介绍之中间件

        本文链接:https://www.haomeiwen.com/subject/ewujxxtx.html