由于一直用业界封装好的如redux-logger、redux-thunk此类的中间件,并没有深入去了解过redux中间件的实现方式。正好前些时间有个需求需要对action执行时做一些封装,于是借此了解了下Redux Middleware的原理。

* 中间件概念

首先简单提下什么是中间件,该部分与下文关系不大,可以跳过。来看眼这个经典的图。

不难发现:

  1. 不使用middleware时,在dispatch(action)时会执行rootReducer,并根据actiontype更新返回相应的state
  2. 而在使用middleware时,简言之,middleware会将我们当前的action做相应的处理,随后再交付rootReducer执行。

简单实现原理

比如现有一个action如下:

function getData() {return {api: '/cgi/getData',type: [GET_DATA, GET_DATA_SUCCESS, GET_DATA_FAIL]}
}

我们希望执行该action时可以发起相应请求,并且根据请求结果由定义的type匹配到相应的reducer,那么可以自定义方法处理该action,因此该方法封装成中间件之前可能是这样的:

async function dispatchPre(action, dispatch) {const api = action.api;const [ fetching_type, success_type,  fail_type] = action.type;// 拉取数据const res = await request(api);// 拉取时状态dispatch({type: fetching_type});// 成功时状态if (res.success) {dispatch({type: success_type, data: res.data});console.log('GET_SUCCESS');}// 失败时状态if (res.fail) {dispatch({type: fail_type});console.log('GET_FAIL');};
}// 调用: dispatchPre(action(), dispatch)

那如何封装成中间件,让我们在可以直接在dispatch(action)时就做到这样呢?可能会首先想到改变dispatch指向

// 储存原来的dispatch
const dispatch = store.dispatch;
// 改变dispatch指向
store.dispatch = dispatchPre;
// 重命名
const next = dispatch;

截止到这我们已经了解了中间件的基本原理了~

源码分析

了解了基本原理能有助于我们更快地读懂middleware的源码。
业务中,一般我们会这样添加中间件并使用。

createStore(rootReducer, applyMiddleware.apply(null, [...middlewares]))

接下来我们可以重点关注这两个函数createStoreapplyMiddleware

CreateStore

// 摘至createStore
export function createStore(reducer, rootState, enhance) {...if (typeof enhancer !== 'undefined') {if (typeof enhancer !== 'function') {throw new Error('Expected the enhancer to be a function.')}/*若使用中间件,这里 enhancer 即为 applyMiddleware()若有enhance,直接返回一个增强的createStore方法,可以类比成react的高阶函数*/return enhancer(createStore)(reducer, preloadedState)}...
}

ApplyMiddleware

再看看applyMiddleware做了什么,applyMiddleware函数非常简单,就十来行代码,这里将其完整复制出来。

export default function applyMiddleware(...middlewares) {return createStore => (...args) => {const store = createStore(...args)let dispatch = () => {throw new Error(`Dispatching while constructing your middleware is not allowed. ` +`Other middleware would not be applied to this dispatch.`)}const middlewareAPI = {getState: store.getState,dispatch: (...args) => dispatch(...args)}// 1、将store对象的基本方法传递给中间件并依次调用中间件const chain = middlewares.map(middleware => middleware(middlewareAPI))// 2、改变dispatch指向,并将最初的dispatch传递给composedispatch = compose(...chain)(store.dispatch)return {...store,dispatch}}
}

执行步骤

根据源码,我们可以将其主要功能按步骤划分如下:

1、依次执行middleware

middleware执行后返回的函数合并到一个chain数组,这里我们有必要看看标准middleware的定义格式,如下

export default store => next => action => {}// 即
function (store) {return function(next) {return function (action) {return {}}}
}

那么此时合并的chain结构如下

[    ...,function(next) {return function (action) {return {}}}
]

2、改变dispatch指向。

想必你也注意到了compose函数,compose函数如下:
[...chain].reduce((a, b) => (...args) => a(b(...args)))
实际就是一个柯里化函数,即将所有的middleware合并成一个middleware,并在最后一个middleware中传入当前的dispatch

*compose可能会看得有点蒙,不理解柯里化函数的同学可以跳到一个例子读懂compose先了解下。

// 假设chain如下:
chain = [a: next => action => { console.log('第1层中间件') return next(action) }b: next => action => { console.log('第2层中间件') return next(action) }c: next => action => { console.log('根dispatch') return next(action) }
]

调用compose(...chain)(store.dispatch)后返回a(b(c(dispatch)))
可以发现已经将所有middleware串联起来了,并同时修改了dispatch的指向。
最后看一下这时候compose执行返回,如下

dispatch = a(b(c(dispatch)))// 调用dispatch(action)
// 执行循序
/*1. 调用 a(b(c(dispatch)))(action) __print__: 第1层中间件2. 返回 a: next(action) 即b(c(dispatch))(action)3. 调用 b(c(dispatch))(action) __print__: 第2层中间件4. 返回 b: next(action) 即c(dispatch)(action)5. 调用 c(dispatch)(action) __print__: 根dispatch6. 返回 c: next(action) 即dispatch(action)7. 调用 dispatch(action)
*/

*一个例子读懂compose

上文提到compose是个柯里化函数,可以看成是将所有函数合并成一个函数并返回的函数。
例如先定义3个方法

function A(x){return x + 'a'
}function B(y){return y + 'b'
}function C(){return 'c'
}var d = [...A, b, C].reduce((a, b) => (d) => {console.log(d, a, b); a(b(d))})d // 打印d// f (d) { console.log(d, a, b); return a(b(d)) }d('d') // 调用d/** d* f(d) { console.log(d, a, b); return a(b(d)) }* f C() { return 'c' }
*//** c* f A(x) { return x + 'a' }* f B(y) { return y + 'b' }
*/

不难发现,使用闭包,在调用d的时候,将ab函数储存在了内存中,调用时会依次将数组从右至左的函数返回做为参数传递给下一个函数使用

十分钟理解Redux中间件相关推荐

  1. java弱引用怎么手动释放,十分钟理解Java中的弱引用,十分钟java引用

    十分钟理解Java中的弱引用,十分钟java引用 本篇文章尝试从What.Why.How这三个角度来探索Java中的弱引用,帮助大家理解Java中弱引用的定义.基本使用场景和使用方法.由于个人水平有限 ...

  2. 十分钟理解Transformer

    本文转载于知乎文章:十分钟理解Transformer Transformer是一个利用注意力机制来提高模型训练速度的模型.关于注意力机制可以参看这篇文章,trasnformer可以说是完全基于自注意力 ...

  3. 十分钟理解线性代数的本质_数学对于编程来说到底有多重要?来看看编程大佬眼里的线性代数!...

    本文提出了一种观点:从应用的角度,我们可以把线性代数视为一门特定领域的程序语言.我们一起来看看!文章有点偏理论讨论,可能比较枯燥,对于一名程序员,你如果看下去,你将会有不一样的收获! 线性代数是什么? ...

  4. 十分钟理解Java泛型擦除

    泛型信息只存在于代码编译阶段,但是在java的运行期(已经生成字节码文件后)与泛型相关的信息会被擦除掉,专业术语叫做类型擦除. 今天我们来讲解泛型中另一个重要知识点--泛型擦除! 泛型擦除概念 泛型信 ...

  5. 十分钟理解线性代数的本质_复习线性代数的正确方式

    有同学对我讲现在复习线性代数遇到了瓶颈,在历年的复习过程中,有许多同学完全找不到复习的感觉,线性代数这门学科的学习方法和高等数学完全不一样,也就是说你学习线性代数首先你得换学习思想,它完全是一套全新的 ...

  6. 十分钟理解线性代数的本质_“线性代数的本质”整理笔记1

    只能说现在的学习环境实在是太太太又友好了. 第一讲:向量.在线性代数中,向量通常都是从原点出发的箭头,而不是物理中理解的,只要方向和长度相同,向量都是等同的.You should think the ...

  7. 十分钟理解javascript中的this对象

    最近在参加的几场面试中都涉及到了对于js中this对象的理解,那么怎样去理解this呢?这里针对不同的场景通过代码来帮助我们理解好this. this到底指向什么? this指向什么呢?一言以蔽之: ...

  8. 十分钟理解logistic回归原理

    关于逻辑回归的分类算法,很多书籍都有介绍,比较来看,还是**李航老师的书<统计学习方法>**里介绍的更清楚,若大家有时间,请不要偷懒,还是建议从头开始看李航老师的书,这本书简洁明了,适合入 ...

  9. c语言ascii码表_新手小白整理C语言笔记备忘,带你十分钟理解C语言

    一.C语言数据类型 1.基本类型:整型.浮点型(单精度.双精度).字符型和枚举类型: 2.构造类型:数组类型.结构体类型和共用体类型: 3.指针类型: 4.空类型.二.数值数据的表示 1.整数:十进制 ...

  10. java actor_十分钟理解Actor模式

    Actor模式是一种并发模型,与另一种模型共享内存完全相反,Actor模型share nothing.所有的线程(或进程)通过消息传递的方式进行合作,这些线程(或进程)称为Actor.共享内存更适合单 ...

最新文章

  1. DT时代下[个推3.0]遵循的四个法则
  2. mysql注入攻击与防御word_SQL注入防御与绕过的几种姿势
  3. java linux 面试题_java 面试题
  4. win10win键无反应_最新Science:强烷基CH键的无定向硼化作用
  5. 一个页面同时发起多个ajax请求,会出现阻塞情况
  6. 导师没有教你的“潜规则”
  7. MongoDB在本地安装与启动
  8. 获取汉字首字母,拼音,可实现拼音字母搜索----npm js-pinyin
  9. Linux 文件类型
  10. 操作系统读写者问题实验报告_Linux操作系统存储子系统核心技术之硬盘与RAID
  11. 德银病危:心比天高,却落下黄粱一梦
  12. jquery导入数据_python大数据实践之三:对分析结果可视化呈现
  13. 华为模拟器eNSP - HCIP - OSPF的Totally STUB 、Totally NSSA综合实验
  14. 陆探一号-中国-2022
  15. 基于Linux的FTP文件传输项目(类似百度云)
  16. 软件测试周刊(第81期):能够对抗消极的不是积极,而是专注;能够对抗焦虑的不是安慰,而是具体。
  17. 典型用户和用户场景描述
  18. 从零实施ERP如何成功
  19. [转载]20行Python代码爬取王者荣耀全英雄皮肤
  20. 200万年薪,西交大2位计算机博士入选华为天才少年

热门文章

  1. (附源码)RN Demo
  2. HTML5 Web SQL 数据库
  3. 软件安装-Mysql数据库
  4. C# Lamda中类似于SQL 中的 In 功能
  5. bootstrap datetimepicker日期插件使用方法
  6. 重复类发展手法_正确护肤手法图解!
  7. ssh互相免密登录_linux服务器之间实现ssh免密码登录的方法
  8. VirtualBox安装的Mac虚拟机,安装增强功能失败,应该是版本太新
  9. 泰山OFFICE正式在UOS应用商店上架
  10. 解决办法:.No package ‘freetype2‘ found