前言

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

  • 中间件执行过程

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

  • redux如何加持中间件

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

  1. * @param {Function} [enhancer] The store enhancer. You may optionally specify it
  2. * to enhance the store with third-party capabilities such as middleware,
  3. * time travel, persistence, etc. The only store enhancer that ships with Redux
  4. * is `applyMiddleware()`.
  5. *
  6. * @returns {Store} A Redux store that lets you read the state, dispatch actions
  7. * and subscribe to changes.
  8. */
  9. export default function createStore(reducer, preloadedState, enhancer) {
  10. if (
  11. (typeof preloadedState === 'function' && typeof enhancer === 'function') ||
  12. (typeof enhancer === 'function' && typeof arguments[3] === 'function')
  13. ) {
  14. throw new Error(
  15. 'It looks like you are passing several store enhancers to ' +
  16. 'createStore(). This is not supported. Instead, compose them ' +
  17. 'together to a single function'
  18. )
  19. }
  20.  
  21. if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
  22. enhancer = preloadedState // 如果初始化state是一个函数,则认为有中间件
  23. preloadedState = undefined
  24. }
  25.  
  26. if (typeof enhancer !== 'undefined') {
  27. if (typeof enhancer !== 'function') {
  28. throw new Error('Expected the enhancer to be a function.')
  29. }
  30.  
  31. return enhancer(createStore)(reducer, preloadedState)
  32. }

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

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

  1. export default function applyMiddleware(...middlewares) { // 将所有中间件存入middlewares数组
  2. return createStore => (...args) => { // 返回函数以createStore为参数,args即[reducer, preloadedState]
  3. const store = createStore(...args) // 创建一个store
  4. let dispatch = () => { // 定义一个dispatch变量指向匿名函数,如果被调用则抛出异常
  5. throw new Error(
  6. `Dispatching while constructing your middleware is not allowed. ` +
  7. `Other middleware would not be applied to this dispatch.`
  8. )
  9. }
  10.  
  11. const middlewareAPI = {
  12. getState: store.getState,
  13. dispatch: (...args) => dispatch(...args) // middlewareAPI的dispatch属性指向一个匿名函数,该函数内部会执行外部dispatch变量指向的那个函数。
  14. }
  15. const chain = middlewares.map(middleware => middleware(middlewareAPI)) // 执行每个中间件,顺带检查是否有中间件调用传入参数中的dispatch,如果有则抛出异常
  16. dispatch = compose(...chain)(store.dispatch) // 将chain展开传入compose,然后执行返回的函数,传入store.dispatch,最后将所有中间件组合成最终的中间件,并将dispatch变量指向这个中间件。
  17.   // 由于dispatch变量的更改,它原来指向的匿名函数现在没有任何变量指向它,会被垃圾回收。
    // 误区:调用middlewareAPI的dispatch属性指向的函数时,内部的dispatch会指向原来抛出异常的匿名函数。这是错误的,在调用middlewareAPI的dispatch属性所指向的函数时,
      // 会寻找dispatch变量,函数内部找不到就向外部作用域寻找,然后找到外部dispatch,而此时外部的dispatch指向最终的中间件,所以会调用最终的中间件。这对于理解redux-thunk非常重要。
  18. return {
  19. ...store,
  20. dispatch // 覆盖store中dispatch变量
  21. }
  22. }
  23. }

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

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

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

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

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

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

  1. [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了。它又会有什么特别之处了,让我们一起看看源码。

  1. function createThunkMiddleware(extraArgument) { // 这里extraArgument完全没用到
  2. return ({ dispatch, getState }) => next => action => { // 这里的dispatch如果有疑惑,请看上面
  3. 再探Redux Middleware的更多相关文章

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

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

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

      1. ViewPager+Fragment再探:和TAB滑动条一起三者结合
      1. Fragment前篇: <Android Fragment初探:静态Fragment组成Activity> ViewPager前篇: <Android ViewPager初探:让页面 ...

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

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

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

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

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

    1.  
    2. 随机推荐

        1. orcl 如何快速删除表中百万或千万数据
        1. orcl 数据库表中数据达到上千万时,已经变的特别慢了,所以时不时需要清掉一部分数据. bqh8表中目前有10000000条数据,需要保留19条数据,其余全部清除掉. 以下为个人方法: 1.首先把需要 ...

        1. [转]Redis学习---Redis高可用技术解决方案总结
        1. [原文]https://www.toutiao.com/i6591646189714670093/ 本文主要针对Redis常见的几种使用方式及其优缺点展开分析. 一.常见使用方式 Redis的几种常见 ...

        1. 使用 jekyll + github pages 搭建个人博客
        1. 1. 新建 github.io 项目 其实 github pages 有两个用途,大家可以在官方网页看到.其中一个是作为个人/组织的主页(每个账号只能有一个),另一个是作为 github 项目的项目主 ...

        1. 数值分析 最小二乘 matlab
        1. 1. 已知函数在下列各点的值为   -1 -0.75 -0.5 0 0.25 0.5 0.75   1.00 0.8125 0.75 1.00 1.3125 1.75 2.3125 分别用一次.二次. ...

        1. 在学习前端的路上,立下一个Flag
        1. 今天开始百度前端学习,以此为证

        1. Docker Java应用日志时间和容器时间不一致
        1. 1.docker容器和系统时间不一致是因为docker容器的原生时区为0时区,而国内系统为东八区. 2.还有容器中运行的java应用打出的日志时间和通过date -R方式获取的容器标准时间有八个小时 ...

        1. 环境搭建(Python
        1. Python 开发环境搭建 Windows   一. Python安装 资源获取 登录Python官网的Windows下载页面https://www.python.org/downloads/win ...

        1. 关于Spring IOC (DI-依赖注入)需要知道的一切
        1. 关联文章: 关于Spring IOC (DI-依赖注入)你需要知道的一切 关于 Spring AOP (AspectJ) 你该知晓的一切 <Spring入门经典>这本书无论对于初学者或者有 ...

        1. Python3.6+nginx+uwsgi部署Django程序到阿里云Ubuntu16.04系统
        1. Python3.6+nginx+uwsgi部署Django程序到阿里云Ubuntu16.04系统 这个是写好的Django程序在本地机运行的情况,一个查询接口. 准备工作 1.首先购买一台阿里云的EC ...

        1. Python2.7-glob
        1. glob 模块,寻找所有匹配指定的模式的路径名,利用的是 Unix shell 的规则,可以在 Windows 环境下使用.模块是通过 os.listdir() fnmatch.fnmatch() ...