将该思想抽象出来,其实和 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. Solr7.0搭建过程

    小李经过Elasticsearch和solr之我为什么选择solr之后决定使用使用Solr作为项目的搜索引擎,然后和同事们开始讨论细节问题. 小李:虽然我会solr4.7版本的搭建,但是人总要有点梦想 ...

  2. 洛谷 P1070 道路游戏 DP

    P1070 道路游戏 题意: 有一个环,环上有n个工厂,每个工厂可以生产价格为x的零钱收割机器人,每个机器人在购买后可以沿着环最多走p条边,一秒走一条,每条边不同时间上出现的金币是不同的,问如何安排购 ...

  3. 杭电多校第十场 hdu6432 Cyclic 打表找规律

    Cyclic Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 524288/524288 K (Java/Others)Total Su ...

  4. 杭电多校第四场 Problem K. Expression in Memories 思维模拟

    Problem K. Expression in Memories Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 262144/262 ...

  5. JOBDU 1027 欧拉回路

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

  6. 理解Yarn的执行流程和组件作用

    Yarn引入案例 1.学生找院长报到,院长给学生一个学号 2.院长比较忙,继续找主任处理学生事务 3.系主任找院办给学生分配资源(书本) 4.主任找张老师教授java 5.张老师给学生安排座位 6.学 ...

  7. Go操作etcd

    etcd是近几年比较火热的一个开源的.分布式的键值对数据存储系统,提供共享配置.服务的注册和发现,本文主要介绍etcd的安装和使用. etcd etcd介绍 etcd是使用Go语言开发的一个开源的.高 ...

  8. Spring Cloud(一):服务注册与发现

    Spring Cloud是什么 Spring Cloud是一系列框架的有序集合.它利用Spring Boot的开发便利性巧妙地简化了分布式系统基础设施的开发,如服务发现注册.配置中心.消息总线.负载均 ...

  9. Vue.js学习总结——1

    1.什么是Vue.js 1.Vue.js 是目前最火的一个前端框架,React是最流行的一个前端框架 2.Vue.js 是前端的主流框架之一,和Angular.js.React.js 一起,并成为前端 ...

  10. 错误:java.lang.Exception: No tests found matching Method testPrePage1(egou_manager_web.TestEBrand) from org.junit.internal.requests.ClassRequest@4f3cc73c

    今天测试分页时出现以下错误: java.lang.Exception: No tests found matching Method testPrePage1(egou_manager_web.Tes ...