将该思想抽象出来,其实和 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. Gym 100956 A Random Points on the Circle

    二分答案. 对于每次二分后的答案来说, 先倍增序列,通过 two point 来找到 以每个点为起点的最优的符合答案的在哪里. 然后可以DFS树去判断他的前k祖先之间的距离是不是大于k. 常数有点大. ...

  2. 牛客暑假多校 H Prefix sum

    题意: 现在有一个2维矩阵, 初始化为0. 并且这个矩阵是及时更新的. dp[i][j] = dp[i-1][j] + dp[i][j-1]; 现在有2种操作: 0 x y   dp[1][x] += ...

  3. CodeForces 103D Time to Raid Cowavans 询问分块

    Time to Raid Cowavans 题意: 询问 下标满足 a + b * k 的和是多少. 题解: 将询问分块. 将b >= blo直接算出答案. 否则存下来. 存下来之后,对于每个b ...

  4. UOJ 34 多项式乘法 FFT 模板

    这是一道模板题. 给你两个多项式,请输出乘起来后的多项式. 输入格式 第一行两个整数 nn 和 mm,分别表示两个多项式的次数. 第二行 n+1n+1 个整数,表示第一个多项式的 00 到 nn 次项 ...

  5. codeforces 822 D. My pretty girl Noora(dp+素数筛)

    题目链接:http://codeforces.com/contest/822/problem/D 题解:做这题首先要推倒一下f(x)假设第各个阶段分成d1,d2,d3...di组取任意一组来说,如果第 ...

  6. odoo12从零开始:一、安装odoo运行环境(windows10)

    前言 鉴于好多朋友说没有mac电脑,windows开发其实也差不了多远,只是个人习惯问题,而且吧,windows的电脑其实配环境也挺快的其实,我在这里再稍微补一个比较简单的windows环境部署,希望 ...

  7. java中父类子类静态代码块、构造代码块执行顺序

    父类静态(代码块,变量赋值二者按顺序执行) 子类静态 父类构造代码块 父类构造方法 子类构造代码块 子类构造方法 普通方法在实列调用的时候执行,肯定位于上面之后了 //父类A public class ...

  8. 【编程之美】用C语言实现状态机(实用)

    版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明. 本文链接:https://www.cnblogs.com/lihuidashen/p/115105 ...

  9. 使用maven的profile构建不同环境配置

    基本概念说明(resources.filter和profile): 1.profiles定义了各个环境的变量id 2.filters中定义了变量配置文件的地址,其中地址中的环境变量就是上面profil ...

  10. Net基础篇_学习笔记_第九天_数组_冒泡排序(面试常见题目)

    冒泡排序: 将一个数组中的元素按照从大到小或从小到大的顺序进行排列. for循环的嵌套---专项课题 int[] nums={9,8,7,6,5,4,3,2,1,0}; 0 1 2 3 4 5 6 7 ...