将该思想抽象出来,其实和 Redux 就无关了。问题变成,怎样实现在截获函数的执行,以在其执行前后添加自己的逻辑。

为了演示,我们准备如下的示例代码来模拟 Redux dispatch action 的场景:

const store = {
dispatch: action => {
console.log("dispating action:", action);
}
}; store.dispatch({ type: "FOO" });

store.dispatch({ type: "BAR" });

我们最终需要实现的效果是 Redux 中 applyMiddleware(...middlewares) 的效果,接收一个中间件数据(函数数组),执行真正的 dispatch 前顺次执行这些中间件。

以打日志为例,我们想在调用 dispatch 时进行日志输出。

尝试1 - 手动

直接的做法就是手动进行。

console.log("before dispatch `FOO`");
store.dispatch({ type: "FOO" });
console.log("before dispatch `FOO`"); console.log("before dispatch BAR");

store.dispatch({ type: "BAR" });

console.log("before dispatch BAR");

但其实这并不算一个系统的解决方案,至少需要摆脱手动这种方式。

尝试2 - 包装

既然所有 dispatch 操作都会打日志,完全有理由抽取一个方法,将 dispatch 进行包装,在这个方法里来做这些事情。

function dispatchWithLog(action) {
console.log(`before dispatch ${action.type}`);
store.dispatch(action);
console.log(`after dispatch ${action.type}`);
}

但调用的地方也得变,不能直接使用原始的 store.disatch 而需要使用封装后的 dispatchWithLog

- store.dispatch({ type: "FOO" });
- store.dispatch({ type: "BAR" });
+ dispatchWithLog({ type: "FOO" });
+ dispatchWithLog({ type: "BAR" });

尝试3 - 替换实现/Monkeypatching

如果我们直接替换掉原始函数的实现,便可以做到调用的地方不受影响而实现新增的 log 功能,虽然修改别人提供的方法容易引起 bug 且不太科学。

const original = store.dispatch;
store.dispatch = function log(action) {
console.log(`before dispatch ${action.type}`);
original(action);
console.log(`after dispatch ${action.type}`);
}; store.dispatch({ type: "FOO" });

store.dispatch({ type: "BAR" });

尝试4 - 多个函数的截获

除了添加 log,如果还想对每次 dispatch 进行错误监控,只需要拿到前面已经替换过实现的 dispatch 方法再次进行替换包装即可。

const original = store.dispatch;
store.dispatch = function log(action) {
console.log(`before dispatch ${action.type}`);
original(action);
console.log(`after dispatch ${action.type}`);
}; const next = store.dispatch;

store.dispatch = function report(action) {

console.log("report middleware");

try {

next(action);

} catch (error) {

console.log(</span>error while dispatching <span class="pl-s1"><span class="pl-pse">${</span><span class="pl-smi">action</span>.<span class="pl-c1">type</span><span class="pl-pse">}</span></span><span class="pl-pds">);

}

};

所以针对单个功能的中间件,我们可以提取出其大概的样子来了:

function middleware(store) {
const next = store.dispatch;
store.dispatch = function(action) {
// 中间件中其他逻辑
next(action);
// 中间件中其他逻辑
};
}

改写日志和错误监控为如下:

function log(store) {
const next = store.dispatch;
store.dispatch = function(action) {
console.log(`before dispatch ${action.type}`);
next(action);
console.log(`after dispatch ${action.type}`);
};
} function report(store) {

const next = store.dispatch;

store.dispatch = function(action) {

console.log("report middleware");

try {

next(action);

} catch (error) {

console.log(</span>error while dispatching <span class="pl-s1"><span class="pl-pse">${</span><span class="pl-smi">action</span>.<span class="pl-c1">type</span><span class="pl-pse">}</span></span><span class="pl-pds">);

}

};

}

然后按需要应用上述中间件即可:

log(store);
report(store);

上面中间件的调用可专门编写一个方法来做:

function applyMiddlewares(store, middlewares) {
middlewares.forEach(middleware => middleware(store));
}

隐藏 Monkeypatching

真实场景下,各中间件由三方编写,如果每个中间件都直接去篡改 store.dispatch 不太科学也不安全。如此的话,中间件只需要关注新添加的逻辑,将新的 dispatch 返回即可,由框架层面拿到这些中间件后逐个调用并重写原来的 dispatch,将篡改的操作收敛。

所以中间件的模式更新成如下:

function middleware(store) {
const next = store.dispatch;
- store.dispatch = function(action) {
+ return function(action) {
// 中间件中其他逻辑
next(action);
// 中间件中其他逻辑
};
}

改写 logreport 中间件:

function log(store) {
const next = store.dispatch;
- store.dispatch = function(action) {
+ return function(action) {
console.log(`before dispatch ${action.type}`);
next(action);
console.log(`after dispatch ${action.type}`);
};
} function report(store) {

const next = store.dispatch;

- store.dispatch = function(action) {

+ return function(action) {

console.log("report middleware");

try {

next(action);

} catch (error) {

console.log(error while dispatching ${action.type});

}

};

}

更新 applyMiddlewares 方法:

function applyMiddlewares(store, middlewares) {
middlewares.forEach(middleware => {
store.dispatch = middleware(store);
});
}

最后,应用中间件:

applyMiddlewares(store, [log, report]);

进一步优化

之所以在应用中间件过程中每次都重新给 store.dispatch 赋值,是想让后续中间件在通过 store.dispatch 访问时,能够拿到前面中间件修改过的 dispatch 函数。

如果中间件中不是直接从 store 身上去获取 store.dispatch,而是前面已经执行过的中间件将新的 dispatch 传递给中间件,则可以避免每次对 store.dispatch 的赋值。

function applyMiddlewares(store, middlewares) {
store.dispatch = middlewares.reduce(
(next, middleware) => middleware(next),
store.dispatch
);
}

忽略掉实际源码中的一些差异,以上,大致就是 Redux 中间件的创建和应用了。

测试

function m1(next) {
return function(action) {
console.log(`1 start`);
next(action);
console.log(`1 end`);
};
}
function m2(next) {
return function(action) {
console.log(`2 start`);
next(action);
console.log(`2 end`);
};
}
function m3(next) {
return function(action) {
console.log(`3 start`);
next(action);
console.log(`3 end`);
}; applyMiddlewares(store, [m1, m2, m3]);

store.dispatch({ type: "FOO" });

store.dispatch({ type: "BAR" });

}

输出结果:

3 start
2 start
1 start
dispating action: { type: 'FOO' }
1 end
2 end
3 end
3 start
2 start
1 start
dispating action: { type: 'BAR' }
1 end
2 end
3 end

相关资源

理解 Redux 的中间件的更多相关文章

  1. Redux的中间件Middleware不难,我信了^_^

    Redux的action和reducer已经足够复杂了,现在还需要理解Redux的中间件.为什么Redux的存在有何意义?为什么Redux的中间件有这么多层的函数返回?Redux的中间件究竟是如何工作 ...

  2. 理解 Redux 中间件机制

    Redux 的 action 是一个 JS 对象,它表明了如何对 store 进行修改.但是 Redux 的中间件机制使action creator 不光可以返回 action 对象,也可以返回 ac ...

  3. Redux的中间件原理分析

    redux的中间件对于使用过redux的各位都不会感到陌生,通过应用上我们需要的所有要应用在redux流程上的中间件,我们可以加强dispatch的功能.最近也有一些初学者同时和实习生在询问中间件有关 ...

  4. 理解Redux以及如何在项目中的使用

    今天我们来聊聊Redux,这篇文章是一个进阶的文章,建议大家先对redux的基础有一定的了解,在这里给大家推荐一下阮一峰老师的文章: http://www.ruanyifeng.com/blog/20 ...

  5. 轻松理解Redux原理及工作流程

    轻松理解Redux原理及工作流程 Redux由Dan Abramov在2015年创建的科技术语.是受2014年Facebook的Flux架构以及函数式编程语言Elm启发.很快,Redux因其简单易学体 ...

  6. 17. react redux的中间件

    1. redux 数据流程图 View 会派发一个 Action Action 通过 Dispatch 方法派发给 Store Store 接收到 Action 连同之前的 State 发给  Red ...

  7. 3.3 理解 Redux 中间件(转)

    这一小节会讲解 redux 中间件的原理,为下一节讲解 redux 异步 action 做铺垫,主要内容为: Redux 中间件是什么 使用 Redux 中间件 logger 中间件结构分析 appl ...

  8. redux进阶 --- 中间件和异步操作

    你为什么需要异步操作? https://stackoverflow.com/questions/34570758/why-do-we-need-middleware-for-async-flow-in ...

  9. 【React】360- 完全理解 redux(从零实现一个 redux)

    点击上方"前端自习课"关注,学习起来~ 前言 记得开始接触 react 技术栈的时候,最难理解的地方就是 redux.全是新名词:reducer.store.dispatch.mi ...

随机推荐

  1. SpringCloud(二)- 服务注册与发现Eureka

    离上一篇微服务的基本概念已经过去了几个月,在写那篇博客之前,自己还并未真正的使用微服务架构,很多理解还存在概念上.后面换了公司,新公司既用了SpringCloud也用了Dubbo+Zookeeper, ...

  2. NLP(二十四)使用LSTM构建生成式聊天机器人

    准备 数据集:AIML数据集 下载数据集并用Notepad++打开,复制到txt文件中方便打开 代码实现 数据很少,训练轮次不多,结果不好,仅当示例 import numpy as np import ...

  3. SCRUM的五个事件

    转自:http://www.scrumcn.com/agile/scrum-knowledge-library/scrum.html#tab-id-7 Scrum 使用固定的事件来产生规律性,以此来减 ...

  4. 【牛客Wannafly挑战赛12】 题解

    传送门:https://www.nowcoder.com/acm/contest/79#question 说是比赛题解,其实我只会前三题: 后面的一定补 T1 题意,在一个长度为n的时间内,问如何选择 ...

  5. Treasure Hunt CodeForces - 979B

    After the big birthday party, Katie still wanted Shiro to have some more fun. Later, she came up wit ...

  6. JOBDU 1027 欧拉回路

    题目1027:欧拉回路 时间限制:1 秒 内存限制:32 兆 特殊判题:否 提交:3620 解决:1847 题目描述:     欧拉回路是指不令笔离开纸面,可画过图中每条边仅一次,且可以回到起点的一条 ...

  7. 题解 bzoj 2151 种树

    题意 传送门 手写堆大法好啊,题解貌似没有结构体堆的做法,思路有些像配对堆,关于配对堆请自行百度,因为本蒟蒻不会.. 以下是蒟蒻的做法:建立一个大根堆a维护最大价值里面存入它的编号以及价值.听说配对堆 ...

  8. 你真的了解Grid布局吗?

    Grid网格布局 概述:Grid将容器划分为一个个网格,通过任意组合不同的网格,做出你想想要的布局 Grid与flex布局相似,将整个Grid分为了容器与子项(格子) Grid容器的三个重要的概念: ...

  9. java8函数式接口详解、函数接口详解、lambda表达式匿名函数、方法引用使用含义、函数式接口实例、如何定义函数式接口

    函数式接口详细定义 函数式接口只有一个抽象方法 由于default方法有一个实现,所以他们不是抽象的. 如果一个接口定义了一个抽象方法,而他恰好覆盖了Object的public方法,仍旧不算做接口的抽 ...

  10. mysql:外键

    mysql:外键 转自:https://www.cnblogs.com/brucemengbm/p/6897410.html 一个班级的学生个人信息表: 什么是外键 在设计的时候,就给表1加入一个外键 ...