• Redux 源码解读 —— 从源码开始学 Redux


    pic
    已经快一年没有碰过 React 全家桶了,最近换了个项目组要用到 React 技术栈,所以最近又复习了一下;捡起旧知识的同时又有了一些新的收获,在这里作文以记之。

    在阅读文章之前,最好已经知道如何使用 Redux(不是 React-Redux)。

    一、准备环境

    为了更好的解读源码,我们可以把源码拷贝到本地,然后搭建一个开发环境。Redux 的使用不依赖于 React,所以你完全可以在一个极为简单的 JavaScript 项目中使用它。这里不再赘述开发环境的搭建过程,需要的同学可以直接拷贝我的代码到本地,然后安装依赖,运行项目。

    $ git clone https://github.com/zhongdeming428/redux && cd redux
    
    $ npm i
    
    $ npm run dev
    

    二、阅读源码

    (1)源代码结构

    忽略项目中的那些说明文档什么的,只看 src 这个源文件目录,其结构如下:

    src
    ├── applyMiddleware.js  // 应用中间件的 API
    ├── bindActionCreators.js   // 转换 actionCreators 的 API
    ├── combineReducers.js  // 组合转换 reducer 的 API
    ├── compose.js  // 工具函数,用于嵌套调用中间件
    ├── createStore.js  // 入口函数,创建 store 的 API
    ├── index.js    // redux 项目的入口文件,用于统一暴露所有 API
    ├── test
    │   └── index.js    // 我所创建的用于调试的脚本
    └── utils   // 专门放工具函数的目录
        ├── actionTypes.js  // 定义了一些 redux 预留的 action type
        ├── isPlainObject.js  // 用于判断是否是纯对象 
        └── warning.js  // 用于抛出合适的警告信息
    
    

    可以看出来 redux 的源码结构简单清晰明了,几个主要的(也是仅有的) API 被尽可能的分散到了单个的文件模块中,我们只需要挨个的看就行了。

    (2)index.js

    上一小节说到 index.js 是 redux 项目的入口文件,用于暴露所有的 API,所以我们来看看代码:

    import createStore from './createStore'
    import combineReducers from './combineReducers'
    import bindActionCreators from './bindActionCreators'
    import applyMiddleware from './applyMiddleware'
    import compose from './compose'
    import warning from './utils/warning'
    import __DO_NOT_USE__ActionTypes from './utils/actionTypes'
    // 不同的 API 写在不同的 js 文件中,最后通过 index.js 统一导出。
    
    // 这个函数用于判断当前代码是否已经被打包工具(比如 Webpack)压缩过,如果被压缩过的话,
    // isCrushed 函数的名称会被替换掉。如果被替换了函数名但是 process.env.NODE_ENV 又不等于 production
    // 的时候,提醒用户使用生产环境下的精简代码。
    function isCrushed() {}
    
    if (
      process.env.NODE_ENV !== 'production' &&
      typeof isCrushed.name === 'string' &&
      isCrushed.name !== 'isCrushed'
    ) {
      warning(
        'You are currently using minified code outside of NODE_ENV === "production". ' +
          'This means that you are running a slower development build of Redux. ' +
          'You can use loose-envify (https://github.com/zertosh/loose-envify) for browserify ' +
          'or setting mode to production in webpack (https://webpack.js.org/concepts/mode/) ' +
          'to ensure you have the correct code for your production build.'
      )
    }
    
    // 导出主要的 API。
    export {
      createStore,
      combineReducers,
      bindActionCreators,
      applyMiddleware,
      compose,
      __DO_NOT_USE__ActionTypes
    }
    

    我删除了所有的英文注释以减小篇幅,如果大家想看原来的注释,可以去 redux 的项目查看源码。

    可以看到在程序的头部引入了所有的 API 模块以及工具函数,然后在底部统一导出了。这一部分比较简单,主要是 isCrushed 函数有点意思。作者用这个函数来判断代码是否被压缩过(判断函数名是否被替换掉了)。

    这一部分也引用到了工具函数,由于这几个函数比较简单,所以可以先看看它们是干嘛的。

    (3)工具函数

    除了 compose 函数以外,所有的工具函数都被放在了 utils 目录下。

    actionTypes.js

    这个工具模块定义了几种 redux 预留的 action type,包括 reducer 替换类型、reducer 初始化类型和随机类型。下面上源码:

    // 定义了一些 redux 保留的 action type。
    // 随机字符串确保唯一性。
    const randomString = () =>
      Math.random()
        .toString(36)
        .substring(7)
        .split('')
        .join('.')
    
    const ActionTypes = {
      INIT: `@@redux/INIT${randomString()}`,
      REPLACE: `@@redux/REPLACE${randomString()}`,
      PROBE_UNKNOWN_ACTION: () => `@@redux/PROBE_UNKNOWN_ACTION${randomString()}`
    }
    
    export default ActionTypes
    

    可以看出就是返回了一个 ActionTypes 对象,里面包含三种类型:INIT、REPLACE 和 PROBE_UNKNOW_ACTION。分别对应之前所说的几种类型,为了防止和用户自定义的 action type 相冲突,刻意在 type 里面加入了随机值。在后面的使用中,通过引入 ActionType 对象来进行对比。

    isPlainObject.js

    这个函数用于判断传入的对象是否是纯对象,因为 redux 要求 action 和 state 是一个纯对象,所以这个函数诞生了。

    上源码:

    /**
     * 判断一个参数是否是纯对象,纯对象的定义就是它的构造函数为 Object。
     * 比如: { name: 'isPlainObject', type: 'funciton' }。
     * 而 isPlainObject 这个函数就不是纯对象,因为它的构造函数是 Function。
     * @param {any} obj 要检查的对象。
     * @returns {boolean} 返回的检查结果,true 代表是纯对象。
     */
    export default function isPlainObject(obj) {
      if (typeof obj !== 'object' || obj === null) return false
    
      let proto = obj
      // 获取最顶级的原型,如果就是自身,那么说明是纯对象。
      while (Object.getPrototypeOf(proto) !== null) {
        proto = Object.getPrototypeOf(proto)
      }
    
      return Object.getPrototypeOf(obj) === proto
    }
    

    warning.js

    这个函数用于抛出适当的警告,没啥好说的。

    /**
     * Prints a warning in the console if it exists.
     *
     * @param {String} message The warning message.
     * @returns {void}
     */
    export default function warning(message) {
      /* eslint-disable no-console */
      if (typeof console !== 'undefined' && typeof console.error === 'function') {
        console.error(message)
      }
      /* eslint-enable no-console */
      try {
        // This error was thrown as a convenience so that if you enable
        // "break on all exceptions" in your console,
        // it would pause the execution at this line.
        throw new Error(message)
      } catch (e) {} // eslint-disable-line no-empty
    }
    

    compose.js

    这个函数用于嵌套调用中间件(middleware),进行初始化。

    /**
     * 传入一系列的单参数函数作为参数(funcs 数组),返回一个新的函数,这个函数可以接受
     * 多个参数,运行时会将 funcs 数组中的函数从右至左进行调用。
     * @param {...Function} funcs 一系列中间件。
     * @returns {Function} 返回的结果函数。
     * 从右至左调用,比如: compose(f, g, h) 将会返回一个新函数
     * (...args) => f(g(h(...args))).
     */
    export default function compose(...funcs) {
      if (funcs.length === 0) {
        return arg => arg
      }
    
      if (funcs.length === 1) {
        return funcs[0]
      }
      // 通过 reduce 方法迭代。
      return funcs.reduce((a, b) => (...args) => a(b(...args)))
    }
    

    (4)createStore.js

    看完了工具函数和入口函数,接下来就要正式步入主题了。我们使用 redux 的重要一步就是通过 createStore 方法创建 store。那么接下来看看这个方法是怎么创建 store 的,store 又是个什么东西呢?

    我们看源码:

    import $$observable from 'symbol-observable'
    // 后面会讲。
    import ActionTypes from './utils/actionTypes'
    // 引入一些预定义的保留的 action type。
    import isPlainObject from './utils/isPlainObject'
    // 判断一个对象是否是纯对象。
    
    // 使用 redux 最主要的 API,就是这个 createStore,它用于创建一个 redux store,为你提供状态管理。
    // 它接受三个参数(第二三个可选),第一个是 reducer,用于改变 redux store 的状态;第二个是初始化的 store,
    // 即最开始时候 store 的快照;第三个参数是由 applyMiddleware 函数返回的 enhancer 对象,使用中间件必须
    // 提供的参数。
    export default function createStore(reducer, preloadedState, enhancer) {
      // 下面这一段基本可以不看,它们是对参数进行适配的。
      /*************************************参数适配****************************************/
      if (
        (typeof preloadedState === 'function' && typeof enhancer === 'function') ||
        (typeof enhancer === 'function' && typeof arguments[3] === 'function')
      ) {
        // 如果传递了多个 enhancer,抛出错误。
        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'
        )
      }
      // 如果没有传递默认的 state(preloadedState 为函数类型,enhancer 为未定义类型),那么传递的
      // preloadedState 即为 enhancer。
      if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
        enhancer = preloadedState
        preloadedState = undefined
      }
    
      if (typeof enhancer !== 'undefined') {
        if (typeof enhancer !== 'function') {
          // 如果 enhancer 为不为空且非函数类型,报错。
          throw new Error('Expected the enhancer to be a function.')
        }
        // 使用 enhancer 对 createStore 进行处理,引入中间件。注意此处没有再传递 enhancer 作为参数。实际上 enhancer 会对 createStore 进行处理,然后返回一个实际意义上的 createStore 用于创建 store 对象,参考 applyMiddleware.js。
        return enhancer(createStore)(reducer, preloadedState)
      }
      // 如果 reducer 不是函数类型,报错。
      if (typeof reducer !== 'function') {
        throw new Error('Expected the reducer to be a function.')
      }
      /*********************************************************************************/
    
      // 在函数内部定义一系列局部变量,用于存储数据。
      let currentReducer = reducer  // 存储当前的 reducer。
      let currentState = preloadedState // 用于存储当前的 store,即为 state。  
      let currentListeners = [] // 用于存储通过 store.subscribe 注册的当前的所有订阅者。
      let nextListeners = currentListeners  // 新的 listeners 数组,确保不直接修改 listeners。
      let isDispatching = false // 当前状态,防止 reducer 嵌套调用。
    
      // 顾名思义,确保 nextListeners 可以被修改,当 nextListeners 与 currentListeners 指向同一个数组的时候
      // 让 nextListeners 成为 currentListeners 的副本。防止修改 nextListeners 导致 currentListeners 发生变化。
      // 一开始我也不是很明白为什么会存在 nextListeners,因为后面 dispatch 函数中还是直接把 nextListeners 赋值给了 currentListeners。
      // 直接使用 currentListeners 也是可以的。后来去 redux 的 repo 搜了搜,发现了一个 issue(https://github.com/reduxjs/redux/issues/2157) 讲述了这个做法的理由。
      // 提交这段代码的作者的解释(https://github.com/reduxjs/redux/commit/c031c0a8d900e0e95a4915ecc0f96c6fe2d6e92b)是防止 Array.slice 的滥用,只有在必要的时候调用 Array.slice 方法来复制 listeners。
      // 以前的做法是每次 dispatch 都要 slice 一次,导致了性能的降低吧。
      function ensureCanMutateNextListeners() {
        if (nextListeners === currentListeners) {
          nextListeners = currentListeners.slice()
        }
      }
    
      // 返回 currentState,即 store 的快照。
      function getState() {
        // 防止在 reducer 中调用该方法,reducer 会接受 state 参数。
        if (isDispatching) {
          throw new Error(
            'You may not call store.getState() while the reducer is executing. ' +
              'The reducer has already received the state as an argument. ' +
              'Pass it down from the top reducer instead of reading it from the store.'
          )
        }
    
        return currentState
      }
    
      // store.subscribe 函数,订阅 dispatch。
      function subscribe(listener) {
        if (typeof listener !== 'function') {
          throw new Error('Expected the listener to be a function.')
        }
        // 不允许在 reducer 中进行订阅。
        if (isDispatching) {
          throw new Error(
            'You may not call store.subscribe() while the reducer is executing. ' +
              'If you would like to be notified after the store has been updated, subscribe from a ' +
              'component and invoke store.getState() in the callback to access the latest state. ' +
              'See https://redux.js.org/api-reference/store#subscribe(listener) for more details.'
          )
        }
    
        let isSubscribed = true
        // 每次操作 nextListeners 之前先确保可以修改。
        ensureCanMutateNextListeners()
        // 存储订阅者的注册方法。
        nextListeners.push(listener)
    
        // 返回一个用于注销当前订阅者的函数。
        return function unsubscribe() {
          if (!isSubscribed) {
            return
          }
    
          if (isDispatching) {
            throw new Error(
              'You may not unsubscribe from a store listener while the reducer is executing. ' +
                'See https://redux.js.org/api-reference/store#subscribe(listener) for more details.'
            )
          }
    
          isSubscribed = false
          // 每次操作 nextListeners 之前先确保可以修改。
          ensureCanMutateNextListeners()
          const index = nextListeners.indexOf(listener)
          nextListeners.splice(index, 1)
        }
      }
    
      // store.dispatch 函数,用于触发 reducer 修改 state。
      function dispatch(action) {
        if (!isPlainObject(action)) {
          // action 必须是纯对象。
          throw new Error(
            'Actions must be plain objects. ' +
              'Use custom middleware for async actions.'
          )
        }
    
        if (typeof action.type === 'undefined') {
          // 每个 action 必须包含一个 type 属性,指定修改的类型。
          throw new Error(
            'Actions may not have an undefined "type" property. ' +
              'Have you misspelled a constant?'
          )
        }
        // reducer 内部不允许派发 action。
        if (isDispatching) {
          throw new Error('Reducers may not dispatch actions.')
        }
    
        try {
          // 调用 reducer 之前,先将标志位置一。
          isDispatching = true
          // 调用 reducer,返回的值即为最新的 state。
          currentState = currentReducer(currentState, action)
        } finally {
          // 调用完之后将标志位置 0,表示 dispatch 结束。
          isDispatching = false
        }
    
        // dispatch 结束之后,执行所有订阅者的函数。
        const listeners = (currentListeners = nextListeners)
        for (let i = 0; i < listeners.length; i++) {
          const listener = listeners[i]
          listener()
        }
    
        // 返回当前所使用的 action,这一步是中间件嵌套使用的关键,很重要。
        return action
      }
    
      // 一个比较新的 API,用于动态替换当前的 reducers。适用于按需加载,代码拆分等场景。
      function replaceReducer(nextReducer) {
        if (typeof nextReducer !== 'function') {
          throw new Error('Expected the nextReducer to be a function.')
        }
        // 执行默认的 REPLACE 类型的 action。在 combineReducers 函数中有使用到这个类型。
        currentReducer = nextReducer
        dispatch({ type: ActionTypes.REPLACE })
      }
    
      // 这是为了适配 ECMA TC39 会议的一个有关 Observable 的提案(参考https://github.com/tc39/proposal-observable)所写的一个函数。
      // 作用是订阅 store 的变化,适用于所有实现了 Observable 的类库(主要是适配 RxJS)。
      // 我找到了引入这个功能的那个 commit:https://github.com/reduxjs/redux/pull/1632。
      function observable() {
        // outerSubscribe 即为外部的 subscribe 函数。
        const outerSubscribe = subscribe
        // 返回一个纯对象,包含 subscribe 方法。
        return {
          subscribe(observer) {
            if (typeof observer !== 'object' || observer === null) {
              throw new TypeError('Expected the observer to be an object.')
            }
    
            // 用于给 subscribe 注册的函数,严格按照 Observable 的规范实现,observer 必须有一个 next 属性。
            function observeState() {
              if (observer.next) {
                observer.next(getState())
              }
            }
    
            observeState()
            const unsubscribe = outerSubscribe(observeState)
            return { unsubscribe }
          },
    
          // $$observable 即为 Symbol.observable,也属于 Observable 的规范,返回自身。
          [$$observable]() {
            return this
          }
        }
      }
      // 初始化时 dispatch 一个 INIT 类型的 action,校验各种情况。
      dispatch({ type: ActionTypes.INIT })
    
      // 返回一个 store 对象。
      return {
        dispatch,
        subscribe,
        getState,
        replaceReducer,
        [$$observable]: observable
      }
    }
    

    不难发现,我们的 store 对象就是一个纯 JavaScript 对象。包含几个属性 API,而我们的 state 就存储在 createStore 这个方法内部,是一个局部变量,只能通过 getState 方法访问到。这里实际上是对闭包的利用,所有我们操作的 state 都存储在 getState 方法内部的一个变量里面。直到我们的程序结束(或者说 store 被销毁),createStore 方法才会被回收,里面的变量才会被销毁。

    而 subscribe 方法就是对观察者模式的利用(注意不是发布订阅模式,二者有区别,不要混淆),我们通过 subscribe 方法注册我们的函数,我们的函数会给存储到 createStore 方法的一个局部变量当中,每次 dispatch 被调用之后,都会遍历一遍 currentListeners,依次执行其中的方法,达到我们订阅的要求。

    (5)combineReducers.js

    了解了 createStore 到底是怎么一回事,我们再来看看 combineReducers 到底是怎么创建 reducer 的。

    我们写 reducer 的时候,实际上是在写一系列函数,然后整个到一个对象的属性上,最后传给 combineReducers 进行处理,处理之后就可以供 createStore 使用了。

    例如:

    // 创建我们的 reducers。
    const _reducers = {
      items(items = [], { type, payload }) {
        if (type === 'ADD_ITEMS') items.push(payload);
        return items;
      },
      isLoading(isLoading = false, { type, payload }) {
        if (type === 'IS_LOADING') return true;
        return false;
      }
    };
    // 交给 combineReducers 处理,适配 createStore。
    const reducers = combineReducers(_reducers);
    // createStore 接受 reducers,创建我们需要的 store。
    const store = createStore(reducers);
    

    那么 combineReducers 对我们的 reducers 对象进行了哪些处理呢?

    下面的代码比较长,希望大家能有耐心。

    import ActionTypes from './utils/actionTypes'
    import warning from './utils/warning'
    import isPlainObject from './utils/isPlainObject'
    
    /**
     * 用于获取错误信息的工具函数,如果调用你所定义的某个 reducer 返回了 undefined,那么就调用这个函数
     * 抛出合适的错误信息。
     * 
     * @param {String} key 你所定义的某个 reducer 的函数名,同时也是 state 的一个属性名。
     * 
     * @param {Object} action 调用 reducer 时所使用的 action。
     */
    function getUndefinedStateErrorMessage(key, action) {
      const actionType = action && action.type
      const actionDescription =
        (actionType && `action "${String(actionType)}"`) || 'an action'
    
      return (
        `Given ${actionDescription}, reducer "${key}" returned undefined. ` +
        `To ignore an action, you must explicitly return the previous state. ` +
        `If you want this reducer to hold no value, you can return null instead of undefined.`
      )
    }
    
    /**
     * 工具函数,用于校验未知键,如果 state 中的某个属性没有对应的 reducer,那么返回报错信息。
     * 对于 REPLACE 类型的 action type,则不进行校验。
     * @param {Object} inputState 
     * @param {Object} reducers 
     * @param {Object} action 
     * @param {Object} unexpectedKeyCache 
     */
    function getUnexpectedStateShapeWarningMessage(
      inputState,
      reducers,
      action,
      unexpectedKeyCache
    ) {
      const reducerKeys = Object.keys(reducers)
      const argumentName =
        action && action.type === ActionTypes.INIT
          ? 'preloadedState argument passed to createStore'
          : 'previous state received by the reducer'
    
      // 如果 reducers 长度为 0,返回对应错误信息。
      if (reducerKeys.length === 0) {
        return (
          'Store does not have a valid reducer. Make sure the argument passed ' +
          'to combineReducers is an object whose values are reducers.'
        )
      }
    
      if (!isPlainObject(inputState)) {
        return (
          `The ${argumentName} has unexpected type of "` +
          {}.toString.call(inputState).match(/s([a-z|A-Z]+)/)[1] +             // {}.toString.call(inputState).match(/s([a-z|A-Z]+)/)[1]
          `". Expected argument to be an object with the following ` +          // 返回的是 inputState 的类型。
          `keys: "${reducerKeys.join('", "')}"`
        )
      }
    
      // 获取所有 State 有而 reducers 没有的属性,加入到 unexpectedKeysCache。
      const unexpectedKeys = Object.keys(inputState).filter(
        key => !reducers.hasOwnProperty(key) && !unexpectedKeyCache[key]
      )
    
      // 加入到 unexpectedKeyCache。
      unexpectedKeys.forEach(key => {
        unexpectedKeyCache[key] = true
      })
    
      // 如果是 REPLACE 类型的 action type,不再校验未知键,因为按需加载的 reducers 不需要校验未知键,现在不存在的 reducers 可能下次就加上了。
      if (action && action.type === ActionTypes.REPLACE) return
    
      if (unexpectedKeys.length > 0) {
        return (
          `Unexpected ${unexpectedKeys.length > 1 ? 'keys' : 'key'} ` +
          `"${unexpectedKeys.join('", "')}" found in ${argumentName}. ` +
          `Expected to find one of the known reducer keys instead: ` +
          `"${reducerKeys.join('", "')}". Unexpected keys will be ignored.`
        )
      }
    }
    
    /**
     * 用于校验所有 reducer 的合理性:传入任意值都不能返回 undefined。
     * @param {Object} reducers 你所定义的 reducers 对象。
     */
    function assertReducerShape(reducers) {
      Object.keys(reducers).forEach(key => {
        const reducer = reducers[key]
        // 获取初始化时的 state。
        const initialState = reducer(undefined, { type: ActionTypes.INIT })
    
        if (typeof initialState === 'undefined') {
          throw new Error(
            `Reducer "${key}" returned undefined during initialization. ` +
              `If the state passed to the reducer is undefined, you must ` +
              `explicitly return the initial state. The initial state may ` +
              `not be undefined. If you don't want to set a value for this reducer, ` +
              `you can use null instead of undefined.`
          )
        }
        // 如果初始化校验通过了,有可能是你定义了 ActionTypes.INIT 的操作。这时候重新用随机值校验。
        // 如果返回 undefined,说明用户可能对 INIT type 做了对应处理,这是不允许的。
        if (
          typeof reducer(undefined, {
            type: ActionTypes.PROBE_UNKNOWN_ACTION()
          }) === 'undefined'
        ) {
          throw new Error(
            `Reducer "${key}" returned undefined when probed with a random type. ` +
              `Don't try to handle ${
                ActionTypes.INIT
              } or other actions in "redux/*" ` +
              `namespace. They are considered private. Instead, you must return the ` +
              `current state for any unknown actions, unless it is undefined, ` +
              `in which case you must return the initial state, regardless of the ` +
              `action type. The initial state may not be undefined, but can be null.`
          )
        }
      })
    }
    
    // 把你所定义的 reducers 对象转化为一个庞大的汇总函数。
    // 可以看出,combineReducers 接受一个 reducers 对象作为参数,
    // 然后返回一个总的函数,作为最终的合法的 reducer,这个 reducer 
    // 接受 action 作为参数,根据 action 的类型遍历调用所有的 reducer。
    export default function combineReducers(reducers) {
      // 获取 reducers 所有的属性名。
      const reducerKeys = Object.keys(reducers)
      const finalReducers = {}
      // 遍历 reducers 的所有属性,剔除所有不合法的 reducer。
      for (let i = 0; i < reducerKeys.length; i++) {
        const key = reducerKeys[i]
    
        if (process.env.NODE_ENV !== 'production') {
          if (typeof reducers[key] === 'undefined') {
            warning(`No reducer provided for key "${key}"`)
          }
        }
    
        if (typeof reducers[key] === 'function') {
          // 将 reducers 中的所有 reducer 拷贝到新的 finalReducers 对象上。
          finalReducers[key] = reducers[key]
        }
      }
      // finalReducers 是一个纯净的经过过滤的 reducers 了,重新获取所有属性名。
      const finalReducerKeys = Object.keys(finalReducers)
    
      let unexpectedKeyCache
      // unexpectedKeyCache 包含所有 state 中有但是 reducers 中没有的属性。
      if (process.env.NODE_ENV !== 'production') {
        unexpectedKeyCache = {}
      }
    
      let shapeAssertionError
      try {
        // 校验所有 reducer 的合理性,缓存错误。
        assertReducerShape(finalReducers)
      } catch (e) {
        shapeAssertionError = e
      }
    
      // 这就是返回的新的 reducer,一个纯函数。每次 dispatch 一个 action,都要执行一遍 combination 函数,
      // 进而把你所定义的所有 reducer 都执行一遍。
      return function combination(state = {}, action) {
        if (shapeAssertionError) {
          // 如果有缓存的错误,抛出。
          throw shapeAssertionError
        }
    
        if (process.env.NODE_ENV !== 'production') {
          // 非生产环境下校验 state 中的属性是否都有对应的 reducer。
          const warningMessage = getUnexpectedStateShapeWarningMessage(
            state,
            finalReducers,
            action,
            unexpectedKeyCache
          )
          if (warningMessage) {
            warning(warningMessage)
          }
        }
    
        let hasChanged = false
        // state 是否改变的标志位。
        const nextState = {}
        // reducer 返回的新 state。
        for (let i = 0; i < finalReducerKeys.length; i++) {   // 遍历所有的 reducer。
          const key = finalReducerKeys[i]  // 获取 reducer 名称。
          const reducer = finalReducers[key]  // 获取 reducer。
          const previousStateForKey = state[key]  // 旧的 state 值。
          const nextStateForKey = reducer(previousStateForKey, action)  // 执行 reducer 返回的新的 state[key] 值。
          if (typeof nextStateForKey === 'undefined') {
            // 如果经过了那么多校验,你的 reducer 还是返回了 undefined,那么就要抛出错误信息了。
            const errorMessage = getUndefinedStateErrorMessage(key, action)
            throw new Error(errorMessage)
          }
          nextState[key] = nextStateForKey
          // 把返回的新值添加到 nextState 对象上,这里可以看出来,你所定义的 reducer 的名称就是对应的 state 的属性,所以 reducer 命名要规范!
          hasChanged = hasChanged || nextStateForKey !== previousStateForKey
          // 检验 state 是否发生了变化。
        }
        // 根据标志位返回对应的 state。
        return hasChanged ? nextState : state
      }
    }
    

    (6)applyMiddleware.js

    combineReducers 方法代码比较多,但是实际逻辑还是很简单的,接下来这个函数代码不多,但是逻辑要稍微复杂一点,它就是应用中间件的 applyMiddleware 函数。这个函数的思想比较巧妙,值得学习。

    import compose from './compose'
    
    // 用于应用中间件的函数,可以同时传递多个中间件。中间件的标准形式为:
    //  const middleware = store => next => action => { /*.....*/ return next(action); }
    export default function applyMiddleware(...middlewares) {
      // 返回一个函数,接受 createStore 作为参数。args 参数即为 reducer 和 preloadedState。
      return createStore => (...args) => {
        // 在函数内部调用 createStore 创建一个 store 对象,这里不会传递 enhancer,因为 applyMiddleware 本身就是在创建一个 enhancer,然后给 createStore 调用。
        // 这里实际上是通过 applyMiddleware 把 store 的创建推迟了。为什么要推迟呢?因为要利用 middleWares 做文章,先初始化中间件,重新定义 dispatch,然后再创建 store,这时候创建的 store 所包含的 dispatch 方法就区别于不传递 enhancer 时所创建的 dispatch 方法了,其中包含了中间件所定义的一些逻辑,这就是为什么中间件可以干预 dispatch 的原因。
        const store = createStore(...args)
        // 这里对 dispatch 进行了重新定义,不管传入什么参数,都会报错,这样做的目的是防止你的中间件在初始化的时候就
        // 调用 dispatch。
        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) // 注意最后 dispatch 的时候不会访问上面报错的那个 dispatch 函数了,因为那个函数被下面的 dispatch 覆盖了。
        }
        // 对于每一个 middleware,都传入 middlewareAPI 进行调用,这就是中间件的初始化。
        // 初始化后的中间件返回一个新的函数,这个函数接受 store.dispatch 作为参数,返回一个替换后的 dispatch,作为新的
        // store.dispatch。
        const chain = middlewares.map(middleware => middleware(middlewareAPI))
        // compose 方法把所有中间件串联起来调用。用最终结果替换 dispatch 函数,之后所使用的所有 store.dispatch 方法都已经是
        // 替换了的,加入了新的逻辑。
        dispatch = compose(...chain)(store.dispatch)
        // 初始化中间件以后,把报错的 dispatch 函数覆盖掉。
    
        /**
         * middle 的标准形式:
         * const middleware = ({ getState, dispatch }) => next => action => {
         *    // ....
         *    return next(action);
         * }
         * 这里 next 是经过上一个 middleware 处理了的 dispatch 方法。
         * next(action) 返回的仍然是一个 dispatch 方法。
         */
    
        return {
          ...store,
          dispatch  // 全新的 dispatch。
        }
      }
    }
    

    代码量真的很少,但是真的很巧妙,这里有几点很关键:

    • compose 方法利用 Array.prototype.reduce 实现中间件的嵌套调用,返回一个全新的函数,可以接受新的参数(上一个中间件处理过的 dispatch),最终返回一个全新的包含新逻辑的 dispatch 方法。你看 middleware 经过初始化后返回的函数的格式:

      next => action => {
        return next(action);
      }
      

      其中 next 可以看成 dispatch,这不就是接受一个 dispatch 作为参数,然后返回一个新的 dispatch 方法吗?原因就是我们可以认为接受 action 作为参数,然后触发 reducer 更改 state 的所有函数都是 dispatch 函数。

    • middleware 中间件经过初始化以后,返回一个新函数,它接受 dispatch 作为参数,然后返回一个新的 dispatch 又可以供下一个 middleware 调用,这就导致所有的 middleware 可以嵌套调用了!而且最终返回的结果也是一个 dispatch 函数。

      最终得到的 dispatch 方法,是把原始的 store.dispatch 方法传递给最后一个 middleware,然后层层嵌套处理,最后经过第一个 middleware 处理过以后所返回的方法。所以我们在调用应用了中间件的 dispatch 函数时,从左至右的经过了 applyMiddleware 方法的所有参数(middleware)的处理。这有点像是包裹和拆包裹的过程。

    • 为什么 action 可以经过所有的中间件处理呢?我们再来看看中间件的基本结构:

      ({ dispatch, getState }) => next => action => {
        return next(action);
      }
      

      我们可以看到 action 进入函数以后,会经过 next 的处理,并且会返回结果。next 会返回什么呢?因为第一个 next 的值就是 store.dispatch,所以看看 store.dispatch 的源码就知道了。

      function dispatch(action) {
        // 省略了一系列操作的代码……
      
        // 返回当前所使用的 action,这一步是中间件嵌套使用的关键。
        return action
      }
      

      没错,store.dispatch 最终返回了 action,由于中间件嵌套调用,所以每个 next 都返回 action,然后又可以供下一个 next 使用,环环相扣,十分巧妙。

    这部分描述的有点拗口,语言捉急但又不想画图,各位还是自己多想想好了。

    (7)bindActionCreators.js

    这个方法没有太多好说的,主要作用是减少大家 dispatch reducer 所要写的代码,比如你原来有一个 action:

    const addItems = item => ({
      type: 'ADD_ITEMS',
      payload: item
    });
    

    然后你要调用它的时候:

    store.dispatch(addItems('item value'));
    

    如果你使用 bindActionCreators:

    const _addItems = bindActionCreators(addItems, store.dispatch);
    

    当你要 dispatch reducer 的时候:

    _addItems('item value');
    

    这样就减少了你要写的重复代码,另外你还可以把所有的 action 写在一个对象里面传递给 bindActionCreators,就像传递给 combineReducers 的对象那样。

    下面看看源码:

    /**
     * 该函数返回一个新的函数,调用新的函数会直接 dispatch ActionCreator 所返回的 action。
     * 这个函数是 bindActionCreators 函数的基础,在 bindActionCreators 函数中会把 actionCreators 拆分成一个一个
     * 的 ActionCreator,然后调用 bindActionCreator 方法。
     * @param {Function} actionCreator 一个返回 action 纯对象的函数。
     * @param {Function} dispatch store.dispatch 方法,用于触发 reducer。
     */
    function bindActionCreator(actionCreator, dispatch) {
      return function() {
        return dispatch(actionCreator.apply(this, arguments))
      }
    }
    
    // 接受一个 actionCreator(或者一个 actionCreators 对象)和一个 dispatch 函数作为参数,
    //  然后返回一个函数或者一个对象,直接执行这个函数或对象中的函数可以让你不必再调用 dispatch。
    export default function bindActionCreators(actionCreators, dispatch) {
      // 如果 actionCreators 是一个函数而非对象,那么直接调用 bindActionCreators 方法进行转换,此时返回
      // 结果也是一个函数,执行这个函数会直接 dispatch 对应的 action。
      if (typeof actionCreators === 'function') {
        return bindActionCreator(actionCreators, dispatch)
      }
    
      // actionCreators 既不是函数也不是对象,或者为空时,抛出错误。
      if (typeof actionCreators !== 'object' || actionCreators === null) {
        throw new Error(
          `bindActionCreators expected an object or a function, instead received ${
            actionCreators === null ? 'null' : typeof actionCreators
          }. ` +
            `Did you write "import ActionCreators from" instead of "import * as ActionCreators from"?`
        )
      }
    
      // 如果 actionCreators 是一个对象,那么它的每一个属性就应该是一个 actionCreator,遍历每一个 actionCreator,
      // 使用 bindActionCreator 进行转换。
      const keys = Object.keys(actionCreators)
      const boundActionCreators = {}
      for (let i = 0; i < keys.length; i++) {
        const key = keys[i]
        const actionCreator = actionCreators[key]
        // 把转换结果绑定到 boundActionCreators 对象,最后会返回它。
        if (typeof actionCreator === 'function') {
          boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
        }
      }
      return boundActionCreators
    }
    

    这部分挺简单的,主要作用在于把 action creator 转化为可以直接使用的函数。

    三、中间件

    看了源码以后,觉得中间件并没有想象中的那么晦涩难懂了。就是一个基本的格式,然后你在你的中间件里面可以为所欲为,最后调用固定的方法,返回固定的内容就完事了。这就是为什么大多数 redux middleware 的源码都很短小精悍的原因。

    看看 redux-thunk 的源码:

    function createThunkMiddleware(extraArgument) {
      return ({ dispatch, getState }) => next => action => {
        if (typeof action === 'function') {
          return action(dispatch, getState, extraArgument);
        }
    
        return next(action);
      };
    }
    
    const thunk = createThunkMiddleware();
    thunk.withExtraArgument = createThunkMiddleware;
    
    export default thunk;
    

    是不是很短很小?那么到底干了什么让它这么受欢迎呢?

    实际上 redux-thunk 可以被认为就是:

    // 这就是典型的 middleware 格式。
    ({ dispatch, getState }) => next => action => {
      // next 就是 dispatch 方法。注释所在的函数就是返回的新的 dispatch。
      // 先判断一下 action 是不是一个函数。
      if (typeof action === 'function') {
        // 如果是函数,调用它,传递 dispatch,getState 和 多余的参数作为 aciton 的参数。
        return action(dispatch, getState, extraArgument);
      }
      // 如果 action 不是函数,直接 nextr调用 action,返回结果就完事儿了。
      return next(action);
    };
    

    怎么样,是不是很简单?它干的事就是判断了一下 action 的类型,如果是函数就调用,不是函数就用 dispatch 来调用,很简单。

    但是它实现的功能很实用,允许我们传递函数作为 store.dispatch 的参数,这个函数的参数应该是固定的,必须符合上面代码的要求,接受 dispatch、getState作为参数,然后这个函数应该返回实际的 action。

    我们也可以写一个自己的中间件了:

    ({ dispatch, getState }) => next => action => {
      return action.then ? action.then(next) : next(action);
    }
    

    这个中间件允许我们传递一个 Promise 对象作为 action,然后会等 action 返回结果(一个真正的 action)之后,再进行 dispatch。

    当然由于 action.then() 返回的不是实际上的 action(一个纯对象),所以这个中间件可能没法跟其他中间件一起使用,不然其他中间件接受不到 action 会出问题。这只是个示例,用于说明中间件没那么复杂,但是我们可以利用中间件做很多事情。

    如果想要了解更加复杂的 redux 中间件,可以参考:

    四、总结

    • Redux 精妙小巧,主要利用了闭包和观察者模式,然后运用了职责链、适配器等模式构建了一个 store 王国。store 拥有自己的领土,要想获取或改变 store 里面的内容,必须通过 store 的各个函数来实现。
    • Redux 相比于 Vuex 而言,代码量更小,定制化程度更低,这就导致易用性低于 Vuex,但是可定制性高于 Vuex。这也符合 Vue 和 React 的风格。
    • Redux 源码比较好懂,读懂源码更易于掌握 Redux 的使用方法,不要被吓倒。
    • Redux 中间件短小精悍,比较实用。如果从使用方法开始学中间件比较难懂的话,可以尝试从源码学习中间件。

    最后,时间紧迫,水平有限,难免存在纰漏或错误,请大家多多包涵、多多指教、共同进步。

    欢迎来我的 GitHub 下载项目源码;或者 Follow me

  • 相关阅读:
    ZOJ2587 Unique Attack(判定最小割唯一性)
    SPOJ371 Boxes(最小费用最大流)
    SGU185 Two shortest(最小费用最大流/最大流)
    POJ2112 Optimal Milking(最大流)
    HDU3996 Gold Mine(最大权闭合子图)
    POJ3680 Intervals(最小费用最大流)
    SPOJ 7258 Lexicographical Substring Search(后缀自动机)
    HDU 4436 str2int(后缀自动机)
    SPOJ 1812 Longest Common Substring II(后缀自动机)
    CodeForces 235C Cyclical Quest(后缀自动机)
  • 原文地址:https://www.cnblogs.com/DM428/p/10080998.html
Copyright © 2020-2023  润新知