• 带你逐行阅读redux源码


    带你逐行阅读redux源码

    redux版本:2019-7-17最新版:v4.0.4
    git 地址:https://github.com/reduxjs/redux/tree/v4.0.4

    redux目录结构

    +-- src              // redux的核心内容目录
    |   +-- utils        // redux的核心工具库
    |   |   +-- actionTypes.js // 一些默认的随机actionTypes
    |   |   +-- isPlainObject.js // 判断是否是字面变量或者new出来的object
    |   |   +-- warning.js // 打印警告的工具类
    |   +-- applyMiddleware.js
    |   +-- bindActionCreator.js
    |   +-- combineReducers.js
    |   +-- compose.js
    |   +-- createStore.js
    |   +-- index.js
    

    1. index.js

    
    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'
    
    /*
     * 这里定义一个空函数,用来判断环境是否是生产环境且代码被压缩
     */
    function isCrushed() {}
    
    if (
      process.env.NODE_ENV !== 'production' &&
      typeof isCrushed.name === 'string' &&
      isCrushed.name !== 'isCrushed'
    ) {
      warning(
        'xxx'
      )
    }
    
    export {
      createStore,
      combineReducers,
      bindActionCreators,
      applyMiddleware,
      compose,
      __DO_NOT_USE__ActionTypes
    }
    
    

    index的代码非常简洁,主要逻辑就是提取各个目录下的文件并提供统一出口,供外部调用。

    isCrushed空函数很巧妙的利用代码压缩工具的原理对当前环境做了一个判断。

    代码压缩工具会将function isCrushed() {}方法压缩成一个单字方法,像这样function a(){},压缩之后isCrushed方法的name自然也变成了a,导致接下来的if判断成立,抛出警告。

    2. createStore.js

    <1> 先看看如何使用

    要研究其原理,必先了解其使用,我们先看看怎么初始化一个store

    
    configureStore(preloadedState) {
        // 配置中间件
        const middlewares = [loggerMiddleware]
        const middlewareEnhancer = applyMiddleware(...middlewares)
        // 配置增强器
        const enhancers = [middlewareEnhancer, monitorReducersEnhancer]
        // 组合增强器
        const composedEnhancers = compose(...enhancers)
        // 创建store
        const store = createStore(rootReducer, preloadedState, composedEnhancers)
        return store
    }
    
    

    <2> 分析:

    1. createStore接收三个参数rootReducer, preloadedState, composedEnhancers
    • rootReducer: 经过combineReducers方法组合后的reducer集合
    • preloadedState:预加载一个state,用来初始化state树,不过此参数较鸡肋,几乎没什么用
    • composedEnhancers:组合后的enhancer集合
    1. 先通过applyMiddleware接入一个中间件列表

    2. 然后把中间件列表作为增强器(enhancers)的一项,和其他增强器组合成一个新的增强器数组

    3. 通过compose方法,组合增强器列表

    4. 传入参数,构建redux store

    这里可以推测出来:中间件肯定是增强器的一个子项,可以把中间件理解为增强器的一部分。

    <3> 参数验证:

    
    import $$observable from 'symbol-observable'
    
    import ActionTypes from './utils/actionTypes'
    import isPlainObject from './utils/isPlainObject'
    
    /**
     * 创建redux仓库
     *
     * @param {Function} reducer 一个接收当前状态和和action动作处理业务后返回新的状态树的方法
     *
     * @param {any} [preloadedState] 初始化状态树
     *
     * @param {Function} [enhancer] store的增强器,用来加强store,可以用来加载中间件
     *
     * @returns {Store} Redux store,可以获取状态树,分发(dispatch)动作(action)
     * 订阅(subscribe)变化
     */
    export default function createStore(reducer, preloadedState, enhancer) {
      /**
       * 这里有四个if检测
       * 
       * 第一个用来提示你不支持直接传入多个enhancer,需要用compose组合enhancer之后再传入
       * 
       * 第二个if用来转换参数,如果createStore只接收两个参数,enhancer的值取第二个参数,
       * 这也是为什么我们createStore的时候可以省略掉preloadedState的原因
       * 
       * 第三个if判断enhancer如果存在且不是一个函数,则抛出enhancer类型错误的提示,如果存在
       * 则执行enhancer方法
       * 
       * 第四个if对reducer进行类型检测,如果不是函数则抛出错误
     */
      if (
        (typeof preloadedState === 'function' && typeof enhancer === 'function') ||
        (typeof enhancer === 'function' && typeof arguments[3] === 'function')
      ) {
        throw new Error(...)
      }
    
      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.')
      }
      
      ...
    }
    
    
    

    首先createStore会在方法执行之前对3个参数进行强类型检测,对不理想的类型进行错误提示。同时对2个参数的情景进行兼容,所以我们在createStore的时候是可以省略第二个参数preloadedState

    <4> 方法总览:

    
    export default function createStore(reducer, preloadedState, enhancer) {
    
      let currentReducer = reducer            // 当前reducer
      let currentState = preloadedState       //  当前状态树
      let currentListeners = []               //  当前订阅者列表
      let nextListeners = currentListeners    //  未来订阅者列表
      let isDispatching = false               //  是否处于分发状态
    
      /**
       * 对订阅者进行浅拷贝,在dispatch时生成订阅者的临时列表副本
       * This makes a shallow copy of currentListeners so we can use
       * nextListeners as a temporary list while dispatching.
       *
       * 确保nextListeners是可以更新的,保证nextListeners !== currentListeners
       * 用来阻止一些中间件产生的bug
       */
      function ensureCanMutateNextListeners() {
        ...
      }
    
      /**
       * 获取state状态树
       *
       * @returns {any} 当前的状态树.
       */
      function getState() {
        ...
      }
    
      /**
       * 订阅方法,在action被dispatch时会通知订阅者,订阅者可以通过getState()方法去获取
       * 最新的状态树
       *
       * @param {Function} listener 监听者,是一个回调函数,会在每次dispatch时执行.
       * @returns {Function} 返回一个移除监听的方法.
       */
      function subscribe(listener) {
        ...
      }
    
      /**
       * 分发(dispatch)动作(action),触发状态树变化的唯一入口
       *
       *
       * @param {Object} action 简单对象
       *
       * @returns {Object} 返回action本事
       *
       * 注意:如果你使用了自定义的中间件,可以对dispatch进行包装,使其返回其他对象
       */
      function dispatch(action) {
        ...
      }
    
      /**
       * 更新reducer,一般用在需要异步路由的大型项目中会使用
       *
       * @param {Function} nextReducer 新的reducer,会替换掉原来的reducer
       * @returns {void}
       */
      function replaceReducer(nextReducer) {
        ...
      }
    
      /**
       * 提供给其他观察者模式/响应式库的交互操作
       * @returns {observable} 
       * https://github.com/tc39/proposal-observable
       */
      function observable() {
        ...
      }
    
      
      // 分发初始化action,获取所有reducer的初始状态
      dispatch({ type: ActionTypes.INIT })
    
      return {
        dispatch,
        subscribe,
        getState,
        replaceReducer,
        [$$observable]: observable
      }
    }
    
    

    createStore返回一个包含dispatch,subscribe,getstate,replaceReducer等方法的对象,并会在内部分发一个初始化状态,用来初始化state状态树。

    <5> ensureCanMutateNextListeners

    function ensureCanMutateNextListeners() {
      if (nextListeners === currentListeners) {
        nextListeners = currentListeners.slice()
      }
    }
    

    此方法很简单,浅拷贝一份订阅者列表至nextListeners
    Array.slice()方法:从已有的数组中返回选定的元素。不传参就是对原数组进行浅拷贝。

    <6> getState

    function getState() {
      if (isDispatching) {
        throw new Error(...)
      }
    
      return currentState
    }
    

    此方法也很简单,先是一个入口判断,不允许在dispatching的时候调用。很粗暴的直接返回当前的状态树(state)。

    注意: 因为Store.getState()获取的就是state本身,基于js的特性我们甚至可以对此对象进行修改,虽然此时会改变state的值,但是却不会通知listener订阅者。所以直接对Store.getState()是一件极其危险的事情。

    <6> subscribe

    function subscribe(listener) {
      // 入口参数校验,只允许传入function
      if (typeof listener !== 'function') {
        throw new Error('Expected the listener to be a function.')
      }
    
      // 不允许在dispatching时订阅
      if (isDispatching) {
        throw new Error(...)
      }
    
      // 订阅标记,标识listener是否被订阅
      let isSubscribed = true
    
      // 保证nextListeners !== currentListeners
      ensureCanMutateNextListeners()
      /**
       * 给nextListeners添加订阅者
       * 这里大家可能会有一个疑问,只是把listener赋给了nextListeners,currentListeners并没
       * 有变化。那么currentListeners是何时进行同步的呢?别急,后面会有答案。
       */
      nextListeners.push(listener)
    
      // 返回值一个取消订阅的函数,这种技巧非常值得我们日常写代码借鉴,避免了我们额外开辟变量去缓存
      // 取消订阅的方法
      return function unsubscribe() {
        // 简单判断,已取消的订阅会被丢弃
        if (!isSubscribed) {
          return
        }
        // 不允许在dispatching时取消订阅
        if (isDispatching) {
          throw new Error(...)
        }
        // 更新订阅标记,置为false,对应unsubscribe入口的判断方法
        isSubscribed = false
    
        // 保证nextListeners !== currentListeners
        ensureCanMutateNextListeners()
        // 从nextListeners中删除当前订阅者
        const index = nextListeners.indexOf(listener)
        nextListeners.splice(index, 1)
      }
    }
    

    理一下subscribe方法的逻辑:

    1. 置一个订阅标识,用来配合取消订阅方法。

    2. nextListeners队列添加订阅者listener

    3. 返回一个取消订阅方法unsubscribe

    注意: 此时currentListeners队列中并没有最新的监听者listener

    <7> dispatch

    function dispatch(action) {
      // isPlainObject是utli里面提供的工具方法,判断action是否是纯粹的对象,dispatch只接收纯粹的对象
      // plainObject:通过"{}"或"new Object"创建的对象
      if (!isPlainObject(action)) {
        throw new Error(...)
      }
      // action必须包含type属性
      if (typeof action.type === 'undefined') {
        throw new Error(...)
      }
      // 同一时刻redux只能dispatch一次
      if (isDispatching) {
        throw new Error('Reducers may not dispatch actions.')
      }
    
      try {
        // 将isDispatching标识置为true
        isDispatching = true
        // 执行reducer方法,并将执行后的结果返回给currentState树
          /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
          // 结合combineReducers.js 174行分析                                     
          // currentReducer执行后 会返回一个按需更新的state树并返回给currentState 
          // 保证了store.getState每次都能拿到**当时**的state树                    
          // 便于数据追踪                                                         
          //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        currentState = currentReducer(currentState, action)
      } finally {
        // 将isDispatching标识置为false
        isDispatching = false
      }
      // 先将nextListeners赋值给currentListeners,然后按队列顺序依次通知订阅者
      const listeners = (currentListeners = nextListeners)
      for (let i = 0; i < listeners.length; i++) {
        const listener = listeners[i]
        listener()
      }
      // 将action原样返回
      return action
    }
    

    dispatch的逻辑:

    1. (全时刻)同步更新isDispatching标识,用来同一时刻reduxdispatch一次。

    2. 执行reducer方法,并将返回值赋给currentState

    3. currentListeners更新为nextListeners

    4. 依次通知订阅者。

    注意: 之前subscribe方法没有同步currentListeners的事情,在dispatch里面做了。这就解决了之前currentListeners不是完整的订阅者列表的问题。

    <8> replaceReducer

    function replaceReducer(nextReducer) {
      // 类型判断
      if (typeof nextReducer !== 'function') {
        throw new Error(...)
      }
    
      // 直接将新的reducer赋值给当前reducer,很简单粗暴
      currentReducer = nextReducer
    
      // 通知一个更新状态,效果与ActionTypes.INIT类似
      dispatch({ type: ActionTypes.REPLACE })
    }
    

    更新reducer,直接用新的reducer覆盖旧的reducer。有些大型项目做了拆分,需要异步加载一些reducer,就会用到此方法替换reducer。一般项目可能很少用。

    <9> observable

    function observable() {
      const outerSubscribe = subscribe
      return {
        subscribe(observer) {
          // 参数验证观察者只能是对象
          if (typeof observer !== 'object' || observer === null) {
            throw new TypeError('Expected the observer to be an object.')
          }
          // 这里是observer通用的规范,用next获取下一次的状态
          function observeState() {
            if (observer.next) {
              observer.next(getState())
            }
          }
    
          observeState()
          // 订阅观察者,并将取消订阅返回
          const unsubscribe = outerSubscribe(observeState)
          return { unsubscribe }
        },
        // $$observable是一个es6的symbol定义,用来标识此变量是observable对象
        [$$observable]() {
          return this
        }
      }
    }
    

    observable日常开发不会用到,一般第三方observe库才会用到。例如redux-observable等rxjs库

    3. compose.js

    /**
     * 组合多个函数
     * 从右到左组合函数,最右边的函数能接收多个参数,然后依次将右侧函数的执行结果作为参数
     * 传给左边的函数
     *
     * @param {...Function} 
     * @returns {Function} 
     */
    
    export default function compose(...funcs) {
      // 没有参数时直接返回一个返回入参的函数
      if (funcs.length === 0) {
        return arg => arg
      }
      // 只有一个参数时,直接返回此函数
      if (funcs.length === 1) {
        return funcs[0]
      }
      // 多个参数时,会返回一个从右至左依次执行函数的函数
      // Array.reduce()方法会循环数组并将上一次计算的结果作为第一个参数(a)返回给下一次循环
      return funcs.reduce((a, b) => (...args) => a(b(...args)))
    }
    

    compose方法非常简短,但是理解起来并不简单,运用了科里化的思想,通过es6的语法糖实现了
    一行代码完成函数的层层传递功能。

    redux洋葱模型式的中间件核心就是通过这个compose方法层层封装(增强)dispatch(dispatch作为修改state的唯一入口,一切state必将流入dispatch)实现的。

    4. applyMiddleware.js

    <1> 先看看如何使用

    官方的一个日志中间件的例子:

    const logger = store => next => action => {
    	console.group(action.type)
    	console.info('dispatching', action)
    	let result = next(action)
    	console.log('next state', store.getState())
    	console.groupEnd()
    	return result
    }
    
    export default logger
    

    <2> 分析

    这个中间件会在每一次dispatch开始和结束的时候进行打印,由于洋葱模型的特性,这个中间
    件建议放在最右边。

    观察中间件的结构,可以看出来中间件的结构其实是一个三层函数的封装。

    function (store) {
      return function(dispatch) {
        return function(action) {
          ...
        }
      }
    }
    

    至于为什么中间件要固定这么写,待会我们从applyMiddleware的源码进行分析。

    <3> 源码解读

    import compose from './compose'
    
    /**
     * 创建中间件去增强dispatch的功能,比如日志记录,异步操作dispatch等
     *
     * @param {...Function} middlewares 中间件链
     * @returns {Function} store enhancer
     */
    export default function applyMiddleware(...middlewares) {
      // 返回一个两层函数
      // 第一层以createStore作为参数的函数
      // 第二层是createStore方法需要接收的参数
      return createStore => (...args) => {
        // 调用createStore方法创建store
        const store = createStore(...args)
        // 常规操作,给dispatch一个不允许执行的默认值
        let dispatch = () => {
          throw new Error(...)
        }
    
        // 提取store部分api作为临时参数
        const middlewareAPI = {
          getState: store.getState,
          dispatch: (...args) => dispatch(...args)
        }
        // 依次执行一次中间件链
        const chain = middlewares.map(middleware => middleware(middlewareAPI))
        // 将dispatch重新赋值为被中间件处理过后的dispatch
        dispatch = compose(...chain)(store.dispatch)
        // 更新store的dispatch
        return {
          ...store,
          dispatch
        }
      }
    }
    

    <4> applyMiddleware流程

    这里需要结合creatStore方法来梳理:

    1. 用户使用applyMiddleware方法加载中间件时:const middlewareEnhancer = applyMiddleware(...middlewares),执行第一层方法return createStore => (...args) => {...}

    2. creatStore方法接收了enhancer参数后,会返回enhancer(createStore)...的结果,enhancer又是applyMiddleware的组合,故此时进入了applyMiddleware的第二层逻辑

    3. enhancer(createStore)...createStore传递给匿名函数return createStore => (...args) => {...}createStore

    4. 执行enhancer(createStore)(reducer, preloadedState)方法,调用const store = createStore(...args)创建store(绕了半天终于开始真正的createStore了)。

    5. 提取store的部分api(getStatedispatch)传递给中间件链,让中间件这个三层函数执行一次(变成return function(dispatch) {...})。

    6. 通过compose方法从右到左组合中间件链,并执行一次此中间件链,将返回值(return function(action) {...})赋值给dispatchdispatch = compose(...chain)(store.dispatch))。

    7. 最终得到一个会依次执行中间件链方法的dispatch,并把dispatch更新给store返回。

    这里运用了大量科里化的思想,读起来难免有些绕,需要来回反复模拟函数执行的顺序才能明白其中的奥妙。

    注意:

    这里有一点同学们可能会疑惑

    let dispatch = () => {
      throw new Error(...)
    }
    
    const middlewareAPI = {
      getState: store.getState,
      dispatch: (...args) => dispatch(...args)
    }
    ...
    dispatch = compose(...chain)(store.dispatch)
    

    dispatch默认给的是一个抛错的函数,而且在(...args) => dispatch(...args)这一行调用时,
    dispatch并没有更新,而是在最后dispatch = compose(...chain)(store.dispatch)才更新的。
    那么,dispatch在执行时到底会不会抛错呢?

    答案是: 不会

    这就是(...args) => dispatch(...args)方法的巧妙之处了。如果此时直接定义dispatch: dispatch的话middlewareAPI.dispatch就会直接指向() => { throw new Error(...) }函数,并且不会再修改。

    但是dispatch: (...args) => dispatch(...args)这一行其实只是做了一个新函数的定义,并没有真正的指向() => { throw new Error(...) }函数。而是指向dispatch本身。所以dispatch即使在后期更新,(...args) => dispatch(...args)方法内的dispatch也会同步更新。这种奇技淫巧是不是很神奇。

    可以看chrome的例子:

    5. combineReducers.js

    <1> getUndefinedStateErrorMessage

    // 如果reducer执行后没有返回值,会抛出异常
    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.`
      )
    }
    

    这行代码很明显,直接根据actiton抛出对应错误提示,不允许reducer的返回值为undefined.

    <2> getUnexpectedStateShapeWarningMessage

    // 错误检测
    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'
    
      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] +
          `". Expected argument to be an object with the following ` +
          `keys: "${reducerKeys.join('", "')}"`
        )
      }
    
      const unexpectedKeys = Object.keys(inputState).filter(
        key => !reducers.hasOwnProperty(key) && !unexpectedKeyCache[key]
      )
    
      unexpectedKeys.forEach(key => {
        unexpectedKeyCache[key] = true
      })
    
      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.`
        )
      }
    }
    

    <3> assertReducerShape

    // reducer错误检测
    // 1. 确保初始化后有值
    // 2. 确保执行任何action后都有返回值
    function assertReducerShape(reducers) {
      Object.keys(reducers).forEach(key => {
        const reducer = reducers[key]
        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.`
          )
        }
    
        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.`
          )
        }
      })
    }
    

    <4> combineReducers

    /**
     * 整合reducers
     *
     * @param {Object} reducers reducers对象字典
     * ex:
     * {
     *    reducerA: function A() {}
     * }
     * reducerA即reducers的key
     * @returns {Function} reducers集合
     */
    export default function combineReducers(reducers) {
      // 获取reducers的key数组
      const reducerKeys = Object.keys(reducers)
      // 过滤不合法的reducers, 将合法的reducers重新更新到finalReducers里
      const finalReducers = {}
      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') {
          finalReducers[key] = reducers[key]
        }
      }
      const finalReducerKeys = Object.keys(finalReducers)
    
      // 确保不会对同一个key报两次警告
      let unexpectedKeyCache
      if (process.env.NODE_ENV !== 'production') {
        unexpectedKeyCache = {}
      }
    
      // 获取reducer的错误
      let shapeAssertionError
      try {
        assertReducerShape(finalReducers)
      } catch (e) {
        shapeAssertionError = e
      }
    
      // 返回一个大的reducer
      // state:createStore里的currentState
      // action:dispatch的action
      // 未来的某个时间(dispatching),这个combination函数会被执行
      return function combination(state = {}, action) {
        if (shapeAssertionError) {
          throw shapeAssertionError
        }
        // 错误检测
        if (process.env.NODE_ENV !== 'production') {
          const warningMessage = getUnexpectedStateShapeWarningMessage(
            state,
            finalReducers,
            action,
            unexpectedKeyCache
          )
          if (warningMessage) {
            warning(warningMessage)
          }
        }
    
        // 标识:是否产生了变化
        let hasChanged = false
        const nextState = {}
        for (let i = 0; i < finalReducerKeys.length; i++) {
          const key = finalReducerKeys[i]
          const reducer = finalReducers[key]
          const previousStateForKey = state[key]
          // 传入当前执行的reducer的state和action
          const nextStateForKey = reducer(previousStateForKey, action)
          // 不允许执行结果为undefined
          if (typeof nextStateForKey === 'undefined') {
            const errorMessage = getUndefinedStateErrorMessage(key, action)
            throw new Error(errorMessage)
          }
          nextState[key] = nextStateForKey
          // 判断数据是否有更新,更新就返回新状态,不更新返回旧状态
          // 因为reducer的通用逻辑是如果对action没有处理,都会默认返回传入的state,
          // 所以这里可以直接使用!==来判断是否相等,引用相等即reducer没有处理任何业务。
          /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
          // 结合createStore.js 204行分析                                                         
          // 此处判断被执行过reducer的state是否更新,如果更新就用nextState,否则直接使用原始state 
          // 从性能方面考虑                                                                       
          // reducer default 应该返回原state,而不是一个新对象                                    
          // 从数据追踪方面考虑                                                                   
          // reducer case 必须返回一个新state,而不是直接在旧state上做修改                        
          /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
          hasChanged = hasChanged || nextStateForKey !== previousStateForKey
        }
        return hasChanged ? nextState : state
      }
    }
    

    combineReducers先是会执行三个错误检测,确保方法的健壮性。

    最终返回一个combination函数,每次dispatch其实实质上就是执行此函数。

    combination会拿到createStorecurrentState这个状态树和dispathaction。依次执行reducers集合里的合法reducer。并返回一个新的nextState状态树给store

    注意:

    dispatch每次执行都会 全量遍历执行 reducer(感觉会不会造成性能浪费?)。reducer只匹配action。所以不同的reducer也可以处理同一个action执行不同的业务逻辑。

    6. bindActionCreators.js

    <1> bindActionCreator

    // 提供一个绑定action的方法,将dispatch动作内置
    // actionCreator: 纯函数
    // function updateList(listData) {
    //     return {
    //         type: 'updateList',
    //         data: listData
    //     }
    // }
    function bindActionCreator(actionCreator, dispatch) {
      return function() {
        return dispatch(actionCreator.apply(this, arguments))
      }
    }
    

    <2> bindActionCreators

    /**
     * 将actionCreators集合转换成bindActionCreator的集合
     *
     * @param {Function|Object} actionCreators 
     *
     * @param {Function} dispatch store.dispatch
     *
     * @returns {Function|Object} 
     */
    export default function bindActionCreators(actionCreators, dispatch) {
      // actionCreators为函数时直接返回bindActionCreator方法
      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进行bindActionCreator包装。
      const boundActionCreators = {}
      for (const key in actionCreators) {
        const actionCreator = actionCreators[key]
        if (typeof actionCreator === 'function') {
          boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
        }
      }
      return boundActionCreators
    }
    

    bindActionCreators是一个辅助方法,能够让我们以方法的形式来调用action。自动dispatch对应的action

    实质上它只是做了这么一个操作 bindActionFoo = (...args) => dispatch(actionCreator(...args))

    bindActionCreators其实在实际使用中并不一定会用到,惟一会使用到 bindActionCreators 的场景是当你需要把 action creator 往下传到一个组件上,却不想让这个组件觉察到 Redux 的存在,而且不希望把 dispatch 或 Redux store 传给它。

    回顾

    redux代码短小精悍,大量集成了科里化思想,读起来可谓绕之又绕。但是通过反复调试追踪,还是可以理清楚作者的思路。

    其中核心思想

    其一是集中式的订阅发布者模式,通过唯一入口dispatch来更新store state树并通知listeners

    其二是applyMiddleware通过多层函数封装实现洋葱模型式的dispatch增强器功能。让使用者们可以为dispatch拓展各种各样的功能。

  • 相关阅读:
    QD提示软件过期a
    病毒conime.exe、mmlucj.exe、severe.exe 查杀办法
    当UG的License服务器换了后, 客户端如何调整?
    怎样理解构造函数和析构函数
    详解Par2 Files
    Nginx的基本使用和配置
    如何使用bootstrap实现轮播图?
    使用相对长度单位rem布局网页内容
    java中如何对汉字进行排序?
    班级日常分享:一天一瞬间!
  • 原文地址:https://www.cnblogs.com/shenshangzz/p/11663665.html
Copyright © 2020-2023  润新知