将该思想抽象出来, 其实和 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(`error while dispatching ${action.type}`);
- }
- };
所以针对单个功能的中间件, 我们可以提取出其大概的样子来了:
- 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(`error while dispatching ${action.type}`);
- }
- };
- }
然后按需要应用上述中间件即可:
- 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);
- // 中间件中其他逻辑
- };
- }
改写 log 和 report 中间件:
- 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
来源: https://www.cnblogs.com/Wayou/p/understanding_redux_middleware.html