前言

在初步了解Redux中间件演变过程之后,继续研究Redux如何将中间件结合。上次将中间件与redux硬结合在一起确实有些难看,现在就一起看看Redux如何加持中间件。

  • 中间件执行过程

希望借助图形能帮助各位更好的理解中间件的执行情况。

  • redux如何加持中间件

现在是时候看看redux是如何将中间件结合了,我们在源码中一探究竟。

* @param {Function} [enhancer] The store enhancer. You may optionally specify it
* to enhance the store with third-party capabilities such as middleware,
* time travel, persistence, etc. The only store enhancer that ships with Redux
* is `applyMiddleware()`.
*
* @returns {Store} A Redux store that lets you read the state, dispatch actions
* and subscribe to changes.
*/
export default function createStore(reducer, preloadedState, enhancer) {
if (
(typeof preloadedState === 'function' && typeof enhancer === 'function') ||
(typeof enhancer === 'function' && typeof arguments[3] === 'function')
) {
throw new Error(
'It looks like you are passing several store enhancers to ' +
'createStore(). This is not supported. Instead, compose them ' +
'together to a single function'
)
} if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
enhancer = preloadedState // 如果初始化state是一个函数,则认为有中间件
preloadedState = undefined
} if (typeof enhancer !== 'undefined') {
if (typeof enhancer !== 'function') {
throw new Error('Expected the enhancer to be a function.')
} return enhancer(createStore)(reducer, preloadedState)
}

如果createStore第二个参数是函数(第二,第三都是函数会抛异常),则redux认为第二个参数是调用applyMiddleware函数的返回值(注释有说明)。

根据return enhancer(createStore)(reducer, preloadedState),说明applyMiddleware返回了一个函数,该函数内还返回了一个函数。那么接下来从applyMiddleware源码中一探究竟。

export default function applyMiddleware(...middlewares) { // 将所有中间件存入middlewares数组
return createStore => (...args) => { // 返回函数以createStore为参数,args即[reducer, preloadedState]
const store = createStore(...args) // 创建一个store
let dispatch = () => { // 定义一个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) // middlewareAPI的dispatch属性指向一个匿名函数,该函数内部会执行外部dispatch变量指向的那个函数。
}
const chain = middlewares.map(middleware => middleware(middlewareAPI)) // 执行每个中间件,顺带检查是否有中间件调用传入参数中的dispatch,如果有则抛出异常
dispatch = compose(...chain)(store.dispatch) // 将chain展开传入compose,然后执行返回的函数,传入store.dispatch,最后将所有中间件组合成最终的中间件,并将dispatch变量指向这个中间件。
  // 由于dispatch变量的更改,它原来指向的匿名函数现在没有任何变量指向它,会被垃圾回收。
// 误区:调用middlewareAPI的dispatch属性指向的函数时,内部的dispatch会指向原来抛出异常的匿名函数。这是错误的,在调用middlewareAPI的dispatch属性所指向的函数时,
  // 会寻找dispatch变量,函数内部找不到就向外部作用域寻找,然后找到外部dispatch,而此时外部的dispatch指向最终的中间件,所以会调用最终的中间件。这对于理解redux-thunk非常重要。
return {
...store,
dispatch // 覆盖store中dispatch变量
}
}
}

上面的代码中还有一点疑惑,compose函数是什么样子,那么我们再探compose。

 * @param {...Function} funcs The functions to compose.
* @returns {Function} A function obtained by composing the argument functions
* from right to left. For example, compose(f, g, h) is identical to doing
* (...args) => f(g(h(...args))). 可以发现,和我们之前写的代码效果一模一样
*/ export default function compose(...funcs) {
if (funcs.length === 0) {
return arg => arg
} if (funcs.length === 1) {
return funcs[0]
} return funcs.reduce((a, b) => (...args) => a(b(...args)))
}

也许你对数组的reduce方法不是很熟,上篇文章篇幅也比较饱满。那么这儿简单讲解下:

[1, 2, 3, 4].reduce((a, b) => { console.log(a, b); return a + b })
// 1 2 可以发现第一次执行,我们拿到数组的第1,2个变量
// 3 3 拿到上次返回的结果和第3个变量
// 6 4 拿到上次返回的结果和第4个变量

最后结果为10,没有打印所以看不出。当然数组存储的也可能是对象,在reduce函数执行时,拿到每个变量的副本(浅拷贝),然后根据你的代码做对应的事。在这就以上篇文章的中间

件为例,再加入logMiddleware3(和logMiddleware2类似,只是将打印的数字部分改为3而已),看看compose函数执行过程。

[logMiddleware3, logMiddleware2, logMiddleware].reduce((a, b) => (...args) => a(b(...args)))
// 假定compose函数传入的参数为store.dispatch,则有以下结果:
// (logMiddleware3, logMiddleware2) => (...args) => logMiddleware3(logMiddleware2(...args)) 这里args[0]为logMiddleware(store.dispatch)返回的中间件
// (logMiddleware3(logMiddleware2(...args)), logMiddleware) => (...args) => logMiddleware3(logMiddleware2(logMiddleware(...args))) 这里的args[0]为store.dispatch
// 最后返回(...args)=> logMiddleware3(logMiddleware2(logMiddleware(...args))) ,接着执行该函数,传入store.dispatch,也就产生了最终的中间件

现在对于redux结合过程已经有了一定的认识,是时候看看别人的中间件了,对比我们自己的中间件,也许有不同的收获。

  • redux-thunk

至此我们写的中间件都比较好理解,是时候认识下redux-thunk了。它又会有什么特别之处了,让我们一起看看源码。

function createThunkMiddleware(extraArgument) { // 这里extraArgument完全没用到
return ({ dispatch, getState }) => next => action => { // 这里的dispatch如果有疑惑,请看上面

再探Redux Middleware的更多相关文章

  1. redux middleware 的理解

    前言 这几天看了redux middleware的运用与实现原理,写了一个百度搜索的demo,实现了类似redux-thunk和redux-logger中间件的功能. 项目地址:https://git ...

  2. 如何学习理解Redux Middleware

    Redux中的middleware其实就像是给你提供一个在action发出到实际reducer执行之前处理一些事情的机会.可以允许我们添加自己的逻辑在这段当中.它提供的是位于 action 被发起之后 ...

  3. 【再探backbone 02】集合-Collection

    前言 昨天我们一起学习了backbone的model,我个人对backbone的熟悉程度提高了,但是也发现一个严重的问题!!! 我平时压根没有用到model这块的东西,事实上我只用到了view,所以昨 ...

  4. ViewPager+Fragment再探:和TAB滑动条一起三者结合

    Fragment前篇: <Android Fragment初探:静态Fragment组成Activity> ViewPager前篇: <Android ViewPager初探:让页面 ...

  5. 再探jQuery

    再探jQuery 前言:在使用jQuery的时候发现一些知识点记得并不牢固,因此希望通过总结知识点加深对jQuery的应用,也希望和各位博友共同分享. jQuery是一个JavaScript库,它极大 ...

  6. [老老实实学WCF] 第五篇 再探通信--ClientBase

    老老实实学WCF 第五篇 再探通信--ClientBase 在上一篇中,我们抛开了服务引用和元数据交换,在客户端中手动添加了元数据代码,并利用通道工厂ChannelFactory<>类创 ...

  7. Spark Streaming揭秘 Day7 再探Job Scheduler

    Spark Streaming揭秘 Day7 再探Job Scheduler 今天,我们对Job Scheduler再进一步深入一下,对一些更加细节的源码进行分析. Job Scheduler启动 在 ...

  8. 再探ASP.NET 5(转载)

    就在最近一段时间,微软又有大动作了,在IDE方面除了给我们发布了Viausl Studio 2013 社区版还发布了全新的Visual Studio 2015 Preview. Visual Stud ...

  9. 再探java基础——break和continue的用法

    再探java基础——break和continue的用法 break break可用于循环和switch...case...语句中. 用于switch...case中: 执行完满足case条件的内容内后 ...

随机推荐

  1. Huawei 常用基本配置命令一

    华为交换机的三种视图: 用户视图, 系统视图, 接口视图 用户视图: 刚开始登入交换机时的视图,一般看到的是尖括号<> . save // 配置完交换机后保存当前配置的命令 system- ...

  2. 怎样在 fedora 28 上 打开 .jnlp 文件

    最近使用 iDrac 和 iLO 总是会使用到 .jnlp 文件, 为了方便,今天把设置过程记录下来. JNLP 文件,全名为 Java Network Launching Protocol 文件, ...

  3. MySQL主从复制半同步复制原理及搭建

    在MySQL5.5之前的版本中,MySQL的复制是异步复制,主库和从库的数据之间存在一定的延迟,比如网络故障等各种原因,这样子容易存在隐患就是:当在主库写入一个事务成功后并提交了,但是由于从库延迟没有 ...

  4. October 23rd, 2017 Week 43rd Monday

    Champions have the courage to keep turning the pages because they know a better chapter lies ahead. ...

  5. SDN负载均衡

    我负责的工作: (1).前期工作思路设计及方案选定.讲解 (2).后期代码修正 (3).视频制作 负载均衡程序 程序流程图 代码 from mininet.topo import Topo class ...

  6. js指定范围随机整数

    js获取指定范围内随机整数,例如 6-10 (m-n) 计算公式: Math.floor(Math.random()*(n-m))+m // 6-10随机数,用循环得出一组测试随机数 var str ...

  7. error occurred at recursive SQL level 1

    ORA-00604: error occurred at recursive SQL level 1出现的错误:java.sql.SQLException: ORA-00604: error occu ...

  8. C#中使用WeiFenLuo.WinFormsUI.Docking.dll实现窗口停靠效果

    很酷的效果,很值得好好去学习的哈. 重置工具箱: 新建一个WinForm程序,项目名称为TestDockPanelControl.选中Form1窗体后选择工具箱--->>新建个添加选项卡命 ...

  9. JS日历控件优化(增加时分秒)

    JS日历控件优化      在今年7月份时候 写了一篇关于 "JS日历控件" 的文章 , 当时只支持 年月日 的日历控件,现在优化如下:      1. 在原基础上 支持 yyyy ...

  10. P1312 Mayan游戏

    题目描述 Mayan puzzle是最近流行起来的一个游戏.游戏界面是一个 7 行5 列的棋盘,上面堆放着一些方块,方块不能悬空堆放,即方块必须放在最下面一行,或者放在其他方块之上.游戏通关是指在规定 ...