til
  • README
  • Software Development Roles
  • solid
  • README
    • service-worker
  • docker
    • arg
    • 更新docker版本
  • editor
    • vscode
    • Creating a VS Code Theme
  • english
    • words
  • front-end
    • ==
    • ECMAScript
    • IIFE
    • Label
    • basic
    • html.js.css渲染顺序
    • npm-vs-yarn
    • obj-delete-key-value
    • react
    • split-join-and-replace
    • video
    • 前端自检清单
    • 递归及去重
    • css
      • css换肤
      • flex
      • list
      • nth-child和nth-of-type区别
      • padding
      • position
      • 层叠上下文
      • 层叠样式(+)
      • 正方形
      • 语义化标签
    • dom
      • DOCTYPE
      • HEAD
      • 修改document
      • 自定义表单验证
    • electron
      • basic
    • es6
      • basic-type
      • basic
      • prototype-example
      • defineProperty
      • understanding-es6
        • 0.introduction
        • Appendix A: Smaller Changes
        • Appendix B: Understanding ES7
        • Block-Binding
        • Proxies&Reflection
        • class
        • 解构赋值
        • function
        • improved-array
        • iterators&generators
        • modules
        • object
        • promise
        • Map&Set
        • symbol
    • images
      • 前端角度看图片
    • interview_case
      • lexical_scope
      • redux和localstroage存储位置
    • javascript
      • fuck-the-js
      • js-engine-work
      • js原生操作dom
      • what-is-function-program
      • 执行上下文
      • articles
        • JavaScript中使用函数组合
        • JavaScript中的依赖注入
        • JavaScript作用域链中的标识符解析和闭包
        • JavaScript是何如工作的--概述
        • JavaScript深拷贝
        • JavaScript的全局变量是如何工作的
        • js继承常见的误解
        • node12&chrome中7个新的提案功能
        • 你真的懂JavaScript吗
      • date
        • index
      • engines
        • basic
        • JavaScript引擎基础:外形和内联缓存
        • v8中推测性优化的介绍
        • 优化原型
        • 更快的异步功能和promise
      • events
        • baisc
        • 事件冒泡和捕获
        • 定义事件
        • 页面生命周期
      • higher-order-function
        • curry
        • monad
      • module
        • basic
        • main&module
      • objects
        • iterator
        • spread
        • examples
          • iterator
      • performance
        • blocking-css
        • cache
      • prototype
        • Property-Descriptors
        • basic
        • prototype-shadow
      • you-dont-known-js
        • async&performance
          • Chapter 1: Asynchrony: Now & Later
          • Chapter 2: Callbacks
          • Chapter 3: Promises
          • Chapter 4: Generators
        • scope & closures
          • apA
          • apB
          • apC
          • apD
          • chapter1-what-is-scope
          • chapter2-lexical-scope
          • chapter3-function-vs-block-scope
          • chapter4-hoisting
          • chapter5-scope-closure
        • this & object prototypes
          • chapter1-this-or-that
          • chapter2-this-make-sense
          • chapter3-objects
          • chapter4-mixing(up)-class-object
          • chapter5-prototype
          • chapter6-behavior-delegation
        • types&grammer
          • Chapter1-Types
          • Chapter2-Values
          • Chapter3-Natives
          • Chapter4-coercion
          • Chapter5-grammer
        • up & going
          • chapter1-into-programming
          • chapter2-into-javascript
          • chapter3-into-YDKJS
    • mobile
      • iPhone分辨率终极指南
    • npm
      • arguments
      • build
    • react-native
      • prop-methods
    • react
      • PropTypes
      • basic
      • codebase-overview
      • component-element-instance
      • context
      • how-to-known-component-is-func-or-class
      • overview
      • react16.9
      • react18计划
      • react的设计原则
      • reconciliation
      • setState
      • useMemo
      • why-do-we-write-super-props
      • 从头实现一个react
      • concurrent
        • 引入并发模式(仅试验)
      • conf
        • conf-2019
      • events
        • 合成事件概述
      • hooks
        • custom-hook
        • effect-hook
        • hooks-api
        • intro
        • overview
        • rules
        • state-hook
        • hooks-vs-class
          • thinking-in-react-hooks
      • overreact
        • Development模式是如何工作的
        • How-Does-setState-Know-What-to-Do
        • Why-Do-React-Elements-Have-a-$$typeof-Property
        • Why-Do-React-Hooks-Rely-on-Call-Order
        • how-to-known-component-is-func-or-class
        • preparing-tach-talk-motivation
        • react作为ui运行
        • things-i-dont-known-as-2018
        • ui-element-problem-and-build-yourself
        • why-do-we-write-super-props
        • 一份完整的useEffect指南
        • 为什么X不是Hook
        • 函数组件与类有什么不同?
        • 演讲准备2-what-why-how
        • 编写弹性组件
        • 让setInterval在React-Hooks中为声明式
      • practice
        • render
      • react-dom
        • basic
      • react-redux
        • apiv7.1-hooks
        • connect
        • shallow-equal
      • redux
        • applyMiddleware
        • applyMiddleware2-细节
        • example
    • regex
      • index
    • stories
      • 数组下标
      • 阻止事件冒泡
    • svelte
      • compile-svelte-in-your-head-1
      • compiler-overview
      • parser
        • 写一个解析器-JavaScript的JSON解析器
    • turbopack
      • basic
    • typescript
      • interface和type的区别
    • webpack
      • hash
      • webpack4-for-react
      • webpack4
      • webpack4to5
      • babel
        • babel-parser和acorn的区别
        • babel.7.11
        • family
        • react16.14使用new-transform
        • update-to-7
    • pdf
      • deep-js
        • basic
      • react
        • reintroducing
  • git
    • capital
    • emoji
  • http
    • http2.0
    • response
  • rails
    • api
    • flash
    • middleware-vs-metal
    • model
    • performance
    • routes
    • environment
      • error
    • patterns
      • service
    • sidekiq
      • params
    • deploy
      • capistrano
        • ssh
  • ruby
    • self
    • net
      • http请求携带cookie
  • server
    • ss
    • ssh
    • user
    • crawler
      • puppeteer
    • nginx
      • domain-without-80
      • nginx节省带宽
  • sql
    • rails
    • search
Powered by GitBook
On this page
  1. front-end
  2. react
  3. redux

applyMiddleware

PreviousreduxNextapplyMiddleware2-细节

Last updated 6 years ago

记得之前第一次看redux源码的时候是很懵逼的,尤其是看到applyMiddleware函数的时候,更是懵逼。当然那也是半年前的事情了,前几天把redux源码看了下,并且实现了个。但是没有实现中间件。今天突然又想看看redux的中间件,就看了起来。

记得半年之前是从函数声明的下一行就开始看不懂了。。。然后前段时间,看了下柯里化函数,加深了高阶函数的印象,所以今天打算把中间件的源码给撸一下。

我们来看看函数声明的下一行,也就是开始看:

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)
    }
    const chain = middlewares.map(middleware => middleware(middlewareAPI))
    dispatch = compose(...chain)(store.dispatch)

    return {
      ...store,
      dispatch
    }
  }
}

从上面我们可以看到中间件返回了函数,返回第一个函数是携带createStore参数的,这个是啥?从名字上我们就可以知道,就是createStore。不过为了证明,我们还是得从源码上来看。

还记得是怎么调用的中间件的吧,大致如下:

const store = createStore(
    reducer,
    applyMiddleware(...middlewares)
);

redux的主要实现都是在createStore里实现的,所以我们主要看createStore里处理参数的部分:

export default function createStore(reducer, preloadedState, enhancer) {
  if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
    enhancer = preloadedState
    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)
  }

  if (typeof reducer !== 'function') {
    throw new Error('Expected the reducer to be a function.')
  }

  //other code
}

从我们调用createStore可以知道第一个参数是reducer,第二个参数就是中间件运行之后返回的携带createStore参数的函数。但是在上面的这段源码里,我们发现是preloadedState来接收这个携带createStore参数的函数,感觉不是很多,命名的'不好'。先继续往下看,wow, 是一个判断,他会判断preloadedState是不是一个函数,第三个参数enhancer是不是未定义;如果preloadedState是函数,enhancer是未定义,那么就会把preloadedState赋值给enhancer,并且设置preloadedState是未定义。 这样就没有问题了,在这里,相当于第三个参数enhancer接收了携带createStore参数的函数。

然后第二个判断:

if (typeof enhancer !== 'undefined') {
  if (typeof enhancer !== 'function') {
    throw new Error('Expected the enhancer to be a function.')
  }

  return enhancer(createStore)(reducer, preloadedState)
}

他会去运行这个enhancer。这个enhancer是什么?就是我们说的携带createStore的函数。

有意思的是,这个enhancer直接在这里运行了,并且采用了createStore作为参数(这个createStore就是函数呀)。 我们再来看看enhancer(createStore)返回的是啥:

return function (...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)
      }
      const chain = middlewares.map(middleware => middleware(middlewareAPI))
      dispatch = compose(...chain)(store.dispatch)

      return {
        ...store,
        dispatch
      }
    }

有意思,返回的是带有多个参数的函数。 上面的代码相当于:

enhancer(createStore) ~= function(...args) => function(reducer, preloadedState)

可以看到,上面的(...args)就是相当于(reducer, preloadedState)。

那么我们再来看看上面的function(...args), 额, 直接在第一行就再次调用创建store,这样不会陷入无限循环吗?不会,因为有参数判断,在createStore的原方法里不会再执行enhancer; 所以我们可以发现,在有中间件的时候,真正的执行createStore是在中间件里去执行的,并且携带的参数是reducer, preloadedState。

所以上面第一行创建了个store对象,他返回的属性有:

{
    dispatch,
    subscribe,
    getState,
    replaceReducer,
    [$$observable]: observable
  }

然后新建了个指向dispatch变量的匿名函数,这个函数在调用的时候抛出异常告诉你不可以在构造中间件时调用dispatch。

Dispatching while constructing your middleware is not allowed. Other middleware would not be applied to this dispatch.

接下来会创建一个middlewareAPI对象:

const middlewareAPI = {
    getState: store.getState, //获取store里的state
    dispatch: (...args) => dispatch(...args) // 调用`dispatch`的时候会抛错,如果在组合中间件之前调用,下面会说
}

一开始我以为是在调用的时候就会报错,可是发现这个对象里的dispatch携带参数,如果只是单纯抛错,完全可以不需要传递参数,然后向下看下去才看到其中的奥妙。

然后就是对中间件集合middlewares(数组)进行操作:

const chain = middlewares.map(middleware => middleware(middlewareAPI)) //返回了新的集合,对应的每个中间件调用的结果
// china是上面返回的中间件的结果
dispatch = compose(...chain)(store.dispatch)
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方法组合(这个放在后面), 会有个...args参数,就是(store.dispatch),等下回说到。

    可能你对reduce不是很熟,可以简单的看下他干了什么事:

    ['1', 2, 3, 'n'].reduce((a, b) => console.log('a is',a , 'b is', b)) // 这样你就会发现这个方法在这里的作用

    其实从注释里也可以知道:

    * @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))).

    把这些中间件都执行到dispatch.

再回到上面看compose的返回:

return funcs.reduce((a, b) => (...args) => a(b(...args)))

我们再看看中间件调用compose的地方:

dispatch = compose(...chain)(store.dispatch)

从这个地方再配合看compose(...chain) => result的这个result.

  • 第一个判断

    返回的是(arg) => arg就是相当于 result(arg) => arg, 果然,直接返回这个store.dispatch

  • 第二个判断

    返回的是唯一的一个中间件result. 然后中间件直接调用store.dispatch作为参数。

  • 最后一个

    这个返回的是一个函数,看起来像这样:

    (...args) => a(b(...args))

    这样就相当于result(args) => a(b(...args)),这样就保证每个中间件都会用到dispatch,并且最终返回这个被扩展过的dispatch.

然后可以看到中间件函数返回了对象:

{
  ...store,
  dispatch
}

这个dispatch就是被处理过的dispatch。

可以看到中间件是在createStore参数里调用的(在参数里运行函数,导致传递给createStore的是中间件运行后返回的结果,从上面的中间件源码可以知道,返回的就是携带createStore参数的函数),现在我们可以看看他是怎么处理中间件返回的函数的。

然后就是组合这些中间件了,这里对高阶函数不熟的,可以看下:

可以看到这个代码,组合了中间件, 使用compose这个高阶函数来处理的。我们看下这个:

简单的redux功能
源码第二行
进createStore函数里
柯里化函数和函数组合
高阶函数