随笔前言

上一周的学习中,我们熟悉了如何通过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工作机制的更多相关文章

  1. 从源码分析Hystrix工作机制

    一.Hystrix解决了什么问题? 在复杂的分布式应用中有着许多的依赖,各个依赖都有难免在某个时刻失败,如果应用不隔离各个依赖,降低外部的风险,那容易拖垮整个应用. 举个电商场景中常见的例子,比如订单 ...

  2. redux:applyMiddleware源码解读

    前言: 笔者之前也有一篇关于applyMiddleware的总结.是applyMiddleware的浅析. 现在阅读了一下redux的源码.下面说说我的理解. 概要源码: step 1:  apply ...

  3. memcached学习笔记——存储命令源码分析上篇

    原创文章,转载请标明,谢谢. 上一篇分析过memcached的连接模型,了解memcached是如何高效处理客户端连接,这一篇分析memcached源码中的process_update_command ...

  4. applyMiddleware源码中的闭包

    闭包都是个老掉牙的话题了,这次又提起,是因为重看Redux源码时发现了applyMiddleware里的用法很巧妙.我们先看一个简单的例子. var a = (num) => num + 1 v ...

  5. [spring源码学习]二、IOC源码——配置文件读取

    一.环境准备 对于学习源码来讲,拿到一大堆的代码,脑袋里肯定是嗡嗡的,所以从代码实例进行跟踪调试未尝不是一种好的办法,此处,我们准备了一个小例子: package com.zjl; public cl ...

  6. 一起学习jQuery2.0.3源码—1.开篇

    write less,do more jQuery告诉我们:牛逼的代码不仅精简而且高效! 2006年1月由美国人John Resig在纽约的barcamp发布了jQuery,吸引了来自世界各地众多Ja ...

  7. OAuth2学习及DotNetOpenAuth部分源码研究

    OAuth2学习及DotNetOpenAuth部分源码研究 在上篇文章中我研究了OpenId及DotNetOpenAuth的相关应用,这一篇继续研究OAuth2. 一.什么是OAuth2 OAuth是 ...

  8. memcached学习笔记——存储命令源码分析下篇

    上一篇回顾:<memcached学习笔记——存储命令源码分析上篇>通过分析memcached的存储命令源码的过程,了解了memcached如何解析文本命令和mencached的内存管理机制 ...

  9. RxJava系列6(从微观角度解读RxJava源码)

    RxJava系列1(简介) RxJava系列2(基本概念及使用介绍) RxJava系列3(转换操作符) RxJava系列4(过滤操作符) RxJava系列5(组合操作符) RxJava系列6(从微观角 ...

随机推荐

  1. JavaScript中callee与caller,apply与call解析

    1. arguments.callee 1.1 解释 返回正被执行的 Function 对象,也就是所指定的 Function 对象的正文. 1,.2 说明 callee 属性的初始值就是正被执行的 ...

  2. Oracle恢复误删除表操作语句

    一.表和数据恢复 1.从回收站里查询被删除的表 select object_name,original_name,partition_name,type,ts_name,createtime,drop ...

  3. Hibernate总结之Hello,World

    1. 引入相关maven依赖: <dependency> <groupId>org.hibernate</groupId> <artifactId>hi ...

  4. Date对象相关函数使用

    参考:http://www.w3school.com.cn/jsref/jsref_obj_date.asp

  5. Spring提供的iBatis的SqlMap配置

    1.    applicationContext.xml <!-- Spring提供的iBatis的SqlMap配置--> <bean id="sqlMapClient&q ...

  6. password passphrase passcode 的区别

    In general, passphrases are long passwords and passcodes are numeric-only passwords.

  7. Mysql储存过程3:if语句

    --if/else语句 if 条件 then SQL语句 else SQL语句elseifSQL语句 end if; create procedure test1( number int ) begi ...

  8. Linux实用命令之xdg-open

    为什么要介绍 xdg-open 呢,得先从需求说起. 一般在控制台中,可以使用命令操作各式文本文件.但难以避免,需要操作一些非文本文件,如 pdf,doc 等. 此时,一般的做法是,打开文件管理器,再 ...

  9. MySQL5.6.26升级到MySQL5.7.9实战方案【转】

    MySQL5.6.26升级到MySQL5.7.9实战方案 转自 MySQL5.6.26升级到MySQL5.7.9实战方案 - 其他网络技术 - 红黑联盟http://www.2cto.com/net/ ...

  10. HOJ 1108

    题目链接:HOJ-1108 题意为给定N和M,找出最小的K,使得K个N组成的数能被M整除.比如对于n=2,m=11,则k=2. 思路是抽屉原理,K个N组成的数modM的值最多只有M个. 具体看代码: ...