Redux学习之解读applyMiddleware源码深入middleware工作机制
随笔前言
在上一周的学习中,我们熟悉了如何通过redux去管理数据,而在这一节中,我们将一起深入到redux的知识中学习。
首先谈一谈为什么要用到middleware
我们知道在一个简单的数据流场景中,点击一个button后,在回调中分发一个action,reducer收到action后就会更新state并通知view重新渲染,如下图所示
但是如果需要打印每一个action来调试,就得去改dispatch或者reducer实现,使其具备打印功能,那么该如何做?因此,需要中间件的加入。
上图展示了应用middleware后的Redux处理事件的逻辑,每个middleware都可以处理一个相对独立的事物,通过串联不同的middleware实现变化多样的功能!
小结:Redux中的reducer更加的专注于转化逻辑,所以middleware是为了增强dispatch而出现的。
middleware是如何工作的
Redux提供了一个applyMiddleware方法来加载middleware,它的源码是这样的:
import compose from './compose';
export default function applyMiddleware(...middlewares) {
return (next) => (reducer, initalState) => {
let store = next(reducer, initalState);
let dispatch = store.dispatch;
let chain = [];
var middlewareAPI = {
getState: store.getState,
dispatch: (action) => dispatch(action)
};
chain = middlewares.map( middleware => middleware(middlewareAPI));
dispatch = compose(...chain)(store.dispatch);
return {
...store,
dispatch
};
}
}
然后我们再上一个logger middleware的源码实现:
export default store => next => action => {
console.log('dispatch:', action);
next(action);
console.log('finish:', action);
}
虽然看到“源码”的那两个字的时候,内心一万只草什么马奔过,但是一看到代码这么精简,这么优美,那就初读一下源码把。
然后
接下来就开始解读上面源码
深入解析middleware运行原理
1. 函数式编程思想设计
middleware是一个层层包裹的匿名函数,这其实是函数式编程的currying(Currying就是把一个带有多个参数的函数拆分成一系列带部分参数的函数)。那么applyMiddleware会对logger这个middleware进行层层的调用,动态的将store和next参数赋值。
那么currying的middleware结构有什么好处呢?
- 1.1 易串联: currying函数具有延迟执行的特性,通过不断currying形成的middleware可以积累参数,再配合组合(compose)的方式,这样很容易就形成pipeline来处理数据流
- 1.2 共享store:在applyMiddleware执行的过程当中,store还是旧的,但是因为闭包的存在,applyMiddleware完成之后,所有的middleware内部拿到的store是最新的且是相同的。
并且applyMiddleware的结构也是一个多层currying的函数,借助compose,applyMiddleware可以用来和其他插件加强createStore函数
2. 给middleware分发store
通过如下方式创建一个普通的store
let newStore = applyMiddleware(mid1, mid2, mid3, ...)(createStore)(reducer, null);
上述代码执行完后,applyMiddleware方法陆续获得了3个参数,第一个是middlewares数组[mid1, mid2, mid3,...],第二个是Redux原生的createStore方法,最后一个是reducer。然后我们可以看到applyMiddleware利用createStore和reducer创建了一个store。而store的getState方法和dispatch方法又分别被直接和间接地赋值给middlewareAPI变量的store
const middleAPI = {
getState: store.getState,
dispatch: (action) => dispatch(action)
}
chain = middlewares.map(middle => middleware(middlewareAPI))
然后,每个middleware带着middlewareAPI这个参数分别执行一遍,执行后,得到一个chain数组[f1, f2, ..., fx, ..., fn],它保存的对象是第二个箭头函数返回的匿名函数。因为是闭包,每个匿名函数多可以访问相同的store,即middlewareAPI.
3.组合串联middleware
这一层只有一行代码,确是applyMiddleware精华所在。
dispatch = compose(...chain)(store.dispatch);
其中,compose是函数式编程中的组合,它将chain中的所有匿名函数[f1, f2, ..., fn]组装成一个新的函数,即新的dispatch。当新的dispatch执行的时候,[f1, f2, ...]会从右到左依次执行。Redux中compose的实现是这样的,当然实现的方式不唯一。
function compose(...funs) {
return arg => funcs.reduceRight( (compose, f) => f(composed), arg)
}
compose(...funcs)返回的是一个匿名函数,其中funcs就是chain数组。当调用reduceRight时,依次从funcs数组的右端取一个函数f(x)拿来执行,f(x)的参数composed就是前一次f(x+1)执行的结果,而第一次执行的f(n)n代表chain的长度,它的参数arg就是store.dispatch。
因此,当compose执行完后,我们得到的dispatch是这样的:
假设n=3:
dispatch = f1(f2(f3(store.dispatch)));
这时调用dispatch,每一个middleware就会依次执行了。
4.在middleware中调用dispatch会发生什么呢?
经过compose后,所有的middleware就算是已经串联起来了。
那么问题来了?
在分发store时,我们有说到每个middleware都可以访问store,也就是我们说的通过middlewareAPI这个变量去拿到dispatch属性进行操作。那么如果在middleware中调用store.dispatch会如何?和调用next()有什么区别?
先上一波代码:
//next()
const logger = store => next => action => {
console.log('dispatch:', action);
next(action);
console.log('finish:', action );
}
//store.dispatch(action);
const logger = store => next => action {
console.log('dispatch:', action);
store.dispatch(action);
console.log('finishL:', action);
}
在分发store的时候,我们有说过:midddleware中的store的dispatch通过匿名函数的方式和最终compose结束后的新dispatch保持一致,所以,在middleware中调用store.dispatch()和在其他任何地方调用其实效果是一样的。
而如果在middleware中调用next(),效果是进入下一个middleware中。
具体如下两个图1和图2所示:
如图1所示,正常情况下,当我们去分发一个action时,middleware会通过next(action)一层层处理和传递action直到redux原生的dispatch。如果某个middleware中使用了store.dispatch(action)来分发action,就会发生如图2所示的情况。这就相当于是又从头开始了。
那么问题又来了,假如这个middleware一直简单粗暴地调用store.dispatch(action),就会形成一个无限循环了,那么store.dispatch(action)的用武之地到底在哪里呢?
假如我们需要发送一个异步请求到服务端获取数据,成功后弹出个message,这里我们通常会用到reduce-thunk这个插件。
const thunk = store => next => action =>
typeof action === 'function' ?
action(store.dispatch, store.getState) :
next(action)
代码很清晰,就是会先判断你dispatch过来的action是不是一个function,如果是则执行action,如果不是则传递到下一个middleware。因此,我们可以这样设计action:
const getMessage = (dispatch, getState) => {
const url = 'http://xxx.json';
Axios.get(url)
.then( res => {
dispatch({
type: 'SHOW_MESSAGE',
message: res.data.data
})
})
.catch( err => {
dispatch({
type: 'ERR_GET',
message: 'error'
})
})
}
如上所示,只要在应用中去调用store.dispatch(getThenShow), redux-thunk这个middleware接收到后就会去执行getMessage方法。getMessage会先请求数据,根据请求结果去做相对应的分发action。这这里的dispatch就是通过redux-thunk这个middleware传递进来的。
总结:在middleware中使用dispatch的场景一般是接受到一个定向action,而这个action又并不希望到达原生的分发action,往往用在异步请求的需求里面。
----------------作者的话:其实看了挺多遍,在脑海中构建整个middleware流程,再结合上周学习Redux时的Demo才渐渐的知其形,还需要多在实践中会其神!--------------
Redux学习之解读applyMiddleware源码深入middleware工作机制的更多相关文章
- 从源码分析Hystrix工作机制
一.Hystrix解决了什么问题? 在复杂的分布式应用中有着许多的依赖,各个依赖都有难免在某个时刻失败,如果应用不隔离各个依赖,降低外部的风险,那容易拖垮整个应用. 举个电商场景中常见的例子,比如订单 ...
- redux:applyMiddleware源码解读
前言: 笔者之前也有一篇关于applyMiddleware的总结.是applyMiddleware的浅析. 现在阅读了一下redux的源码.下面说说我的理解. 概要源码: step 1: apply ...
- memcached学习笔记——存储命令源码分析上篇
原创文章,转载请标明,谢谢. 上一篇分析过memcached的连接模型,了解memcached是如何高效处理客户端连接,这一篇分析memcached源码中的process_update_command ...
- applyMiddleware源码中的闭包
闭包都是个老掉牙的话题了,这次又提起,是因为重看Redux源码时发现了applyMiddleware里的用法很巧妙.我们先看一个简单的例子. var a = (num) => num + 1 v ...
- [spring源码学习]二、IOC源码——配置文件读取
一.环境准备 对于学习源码来讲,拿到一大堆的代码,脑袋里肯定是嗡嗡的,所以从代码实例进行跟踪调试未尝不是一种好的办法,此处,我们准备了一个小例子: package com.zjl; public cl ...
- 一起学习jQuery2.0.3源码—1.开篇
write less,do more jQuery告诉我们:牛逼的代码不仅精简而且高效! 2006年1月由美国人John Resig在纽约的barcamp发布了jQuery,吸引了来自世界各地众多Ja ...
- OAuth2学习及DotNetOpenAuth部分源码研究
OAuth2学习及DotNetOpenAuth部分源码研究 在上篇文章中我研究了OpenId及DotNetOpenAuth的相关应用,这一篇继续研究OAuth2. 一.什么是OAuth2 OAuth是 ...
- memcached学习笔记——存储命令源码分析下篇
上一篇回顾:<memcached学习笔记——存储命令源码分析上篇>通过分析memcached的存储命令源码的过程,了解了memcached如何解析文本命令和mencached的内存管理机制 ...
- RxJava系列6(从微观角度解读RxJava源码)
RxJava系列1(简介) RxJava系列2(基本概念及使用介绍) RxJava系列3(转换操作符) RxJava系列4(过滤操作符) RxJava系列5(组合操作符) RxJava系列6(从微观角 ...
随机推荐
- java基础-关键词super与this
转发:itbooks this是调用自己本身的构造函数,而super是调用父类中的构造函数. 这两个关键词是用在构造函数中的,这两个关键词的设计也是对封装特性的一种考虑,避免编写不必要的重复代码. c ...
- 2017北京国庆刷题Day6 afternoon
期望得分:100+100+40=240 实际得分:100+0+40=140 二进制拆分.二进制前缀和 #include<cstdio> #include<iostream> u ...
- 接口自动化测试框架HttpRunner
接口自动化测试框架 https://github.com/HttpRunner/HttpRunner http://debugtalk.com/post/ApiTestEngine-api-test- ...
- 为什么需要 Stream
Stream 作为 Java 8 的一大亮点,它与 java.io 包里的 InputStream 和 OutputStream 是完全不同的概念.它也不同于 StAX 对 XML 解析的 Strea ...
- NYOJ 35 表达式求值 (字符串处理)
题目链接 描述 ACM队的mdd想做一个计算器,但是,他要做的不仅仅是一计算一个A+B的计算器,他想实现随便输入一个表达式都能求出它的值的计算器,现在请你帮助他来实现这个计算器吧. 比如输入:&quo ...
- Warning: Permanently added the RSA host key for IP address '192.30.253.113' to the list of known hosts. Permission denied (publickey). fatal: Could not read from remote repository. Please make sure y
这个应该是很多github新手经常出错的问题,这个就是没有在你github上添加一个公钥. 下面就直接说步骤: 1 可以用 ssh -T git@github.com去测试一下 图上可以明显看出缺少了 ...
- perl6正则 4: before / after 代码断言: <?{}> / <!{}>
<?before> <? befor XXX> 某字符在 xxx 之前 <?after > <?after XXX> 某字符之后有XXX 对应的取反分别 ...
- http 之cookie和session
cookie和session 关于http: 1.http是:无状态.短连接 2.http的请求生命周期:给服务端发送一个请起头,通过域名提取url,通过路由关系匹配,再通过函数+html进行模板加 ...
- 深入解析Mysql 主从同步延迟原理及解决方案
MySQL的主从同步是一个很成熟的架构,优点为:①在从服务器可以执行查询工作(即我们常说的读功能),降低主服务器压力;②在从主服务器进行备份,避免备份期间影响主服务器服务;③当主服务器出现问题时,可以 ...
- git版本控制系统常见操作总结
简介 Git是强大的版本控制系统,主要功能是针对代码.配置文件等文本进行版本控制.备份等,同时个人认为还是分发代码的一个不错的方式. 常见用法 #创建远程git仓库 [root@test88 ~]# ...