Redux(五):源码分析之combineReducers

2020-06-01 14:37:48 浏览数 (1)

一、combineReducers的作用

该方法用来将多个子reducer组合成一个根reducer。之前手写的版本:

代码语言:javascript复制
function combineReducers(initialState,reducers){
  return function(state=initialState,action){
    return Object.entries(reducers).reduce((init,item)=>{
      const key = item[0];
      const callback = item[1];
      init[key] = callback(state[key],action);
      return init;
    },{});
  }
}

对比下官方原本的代码。

二、combineReducers.js源码分析

5行——15行

定义getUndefinedStateErrorMessage()函数,用以返回错误信息。

代码语言:javascript复制
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.`
  )
}

任何一个子reducer不应该返回undefined,如果没有返回值至少也应该返回null。

99行——178行

返回根reducer函数。

代码语言:javascript复制
/**
 * Turns an object whose values are different reducer functions, into a single
 * reducer function. It will call every child reducer, and gather their results
 * into a single state object, whose keys correspond to the keys of the passed
 * reducer functions.
 *
 * @param {Object} reducers An object whose values correspond to different
 * reducer functions that need to be combined into one. One handy way to obtain
 * it is to use ES6 `import * as reducers` syntax. The reducers may never return
 * undefined for any action. Instead, they should return their initial state
 * if the state passed to them was undefined, and the current state for any
 * unrecognized action.
 *
 * @returns {Function} A reducer function that invokes every reducer inside the
 * passed object, and builds a state object with the same shape.
 */
export default function combineReducers(reducers) {
  const reducerKeys = Object.keys(reducers)
  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)

  let unexpectedKeyCache
  if (process.env.NODE_ENV !== 'production') {
    unexpectedKeyCache = {}
  }

  let shapeAssertionError
  try {
    assertReducerShape(finalReducers)
  } catch (e) {
    shapeAssertionError = e
  }

  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]
      const nextStateForKey = reducer(previousStateForKey, action)
      if (typeof nextStateForKey === 'undefined') {
        const errorMessage = getUndefinedStateErrorMessage(key, action)
        throw new Error(errorMessage)
      }
      nextState[key] = nextStateForKey
      hasChanged = hasChanged || nextStateForKey !== previousStateForKey
    }
    return hasChanged ? nextState : state
  }
}

首先,combineReducers()的参数是一个纯对象,对象的key应与初始state树的key一一对应,value就是处理对应的key的相关reducer。

116——131行:

生成3个常量:

  • finalReducers:数组类型,存放符合value为函数类型的reducer键值对。
  • finalReducerKeys:数组类型,存放key值。

对传入的参数进行遍历整理,finalReducers常量用来存放符合value为函数类型的键值对。

133——143行:

代码语言:javascript复制
let unexpectedKeyCache
if (process.env.NODE_ENV !== 'production') {
  unexpectedKeyCache = {}
}

let shapeAssertionError
try {
  assertReducerShape(finalReducers)
} catch (e) {
  shapeAssertionError = e
}

这里调用了assertReducerShape()函数,函数的作用下边有说明。

shapeAssertionError用于捕获异常信息。

unexpectedKeyCache用于存放那些不存在于preloadedState中的key。

146——160行:

代码语言:javascript复制
    if (shapeAssertionError) {
      throw shapeAssertionError
    }

    if (process.env.NODE_ENV !== 'production') {
      const warningMessage = getUnexpectedStateShapeWarningMessage(
        state,
        finalReducers,
        action,
        unexpectedKeyCache
      )
      if (warningMessage) {
        warning(warningMessage)
      }
    }

getUnexpectedStateShapeWarningMessage()函数的作用参见下边说明。

162——176行:

代码语言:javascript复制
    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]
      const nextStateForKey = reducer(previousStateForKey, action)
      if (typeof nextStateForKey === 'undefined') {
        const errorMessage = getUndefinedStateErrorMessage(key, action)
        throw new Error(errorMessage)
      }
      nextState[key] = nextStateForKey
      hasChanged = hasChanged || nextStateForKey !== previousStateForKey
    }
    return hasChanged ? nextState : state

这块才是combineReducers正真的主体,实现思路和之前手写的一样。唯一不同的就是hasChanged这个变量,如果state没有发生变化则返回上一个previousState,否则返回nextState,虽然reducer无论如何总会执行,但如果本质上state没有改变那么也就没有必然返回一个引用地址不同但值相同的nextState。state是对象,对象是按地址引用的,如果使用了React,这样可以避免一些重复无意义的渲染。

65行——97行

定义assertReducerShape()函数。

代码语言:javascript复制
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.`
      )
    }
  })
}

总结起来就是:

  1. 任何子reducer的返回值都不应该是undefined。
  2. 如果没有匹配到action.type,则返回初始initialState。

17行——63行

定义getUnexpectedStateShapeWarningMessage()函数。

代码语言:javascript复制
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.`
    )
  }
}

这个函数会在非生产版本下执行,总结起来就是:

  1. combineReducers的参数不可以是一个空对象。
  2. 初始preloadedState必须是一个纯对象。
  3. combineReducers的参数key应该与preloadedState相对应。

三、总结

  1. combineReducers的参数必须是一个纯对象
  2. 对象参数的value必须是函数
  3. 对象参数的key必须与preloadedState的key有对应
  4. reducer不可以返回undefined

0 人点赞