Redux介绍之中间件

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。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容