将该思想抽象出来,其实和 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. JavaScript label语句

    使用label 语句可以在代码中添加标签,以便将来使用. 以下是label 语句的语法: label: statement 下面是一个示例: start: for (var i=0; i < c ...

  2. 你真的了解MyBatis中${}和#{}的区别吗?

    动态sql是mybatis的主要特性之一.在mapper中定义的参数传到xml中之后,在查询之前mybatis会对其进行动态解析. mybatis提供了两种支持动态sql的语法:#{} 和 ${}. ...

  3. poj2396 Budget 上下界可行流

    Budget:http://poj.org/problem?id=2396 题意: 给定一个棋盘,给定每一行每一列的和,还有每个点的性质.求一个合理的棋盘数值放置方式. 思路: 比较经典的网络流模型, ...

  4. Codeforces 369 C Valera and Elections

    Valera and Elections 题意:现在有n个候选人, 有n-1条路, 如果选择了这个候选人, 这个候选人就会将从自己这个城市到1号城市上所有坏的路都修复一下,现在求最小的候选人数目, 如 ...

  5. Codefroces 366 C Dima and Salad(dp)

    Dima and Salad 题意:一共有n种水果,每种水果都有一个ai, bi,现求一个最大的ai总和,使得ai之和/对应的bi之和的值等于K. 题解:将bi转换成偏移量,只要偏移到起点位置,就代表 ...

  6. 网络流---最大流(Edmond-Karp算法)的学习

    先上个代码,等有空补充详解 #include<iostream> #include<cstdio> #include<cstring> #include<cm ...

  7. Django系列---使用MySql数据库

    目录 1. 创建数据库 1.1. 使用utf8mb4编码 1.1.1. 确定mysql的配置文件 1.1.2. 修改配置文件 1.1.3. 重启数据库服务,检查相关字段 1.1.4. 新建数据库 1. ...

  8. 带你深入了解NPM——NPM初学者指南

    前段时间,我们邀请了我们“城内”(葡萄城)资深开发工程师刘涛为大家分享了一次干货满满的关于Electron线上公开课,在课程过程中有不少同学对于NPM的概念和用法有一些疑问,所以这次我们希望通过这篇文 ...

  9. linux下使用yum安装新版php7.0

    这两天又装了一下虚拟机,又要编译lnmp,还要弄各种拓展,很麻烦,能不能直接yum安装呢?答案是可以的! 1.首先要更新yum源,不然是默认的老版本,一般都在5.6及以下,但是php7都出来好久了,性 ...

  10. 一个例子明白 javascript 中 for 与 for in 的区别

    var arr = new Array(); arr["a"] = "aa"; arr["b"] = "bb"; arr ...