带你逐行阅读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拓展各种各样的功能。

posted @ 2019-10-12 20:02  参与商  阅读(202)  评论(0编辑  收藏  举报