Redux源码浅析系列(二):`combineReducer`

上一章,我们讲解了createStore。下面,我们来看一下combineReducer
redux中,我们禁止在应用中创建多个store(我们这里默认讨论的都是客户端应用,同构应用不适用这条规则)。

然而,随着应用变得越来越复杂,需要对 reducer 函数 进行拆分,拆分后的每一块独立负责管理 state 的一部分。

combineReducer就是将一个由多个reducer函数作为valueobject,合并成一个rootReducer。然后就可以对这个reducer调用createStore

下面,我们来具体看一下源码:

import { ActionTypes } from './createStore'
import isPlainObject from 'lodash/isPlainObject'
import warning from './utils/warning'

function getUndefinedStateErrorMessage(key, action) {
  const actionType = action && action.type
  const actionName = (actionType && `"${actionType.toString()}"`) || 'an action'

  return (
    `Given action ${actionName}, 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.`
  )
}

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 (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.`
    )
  }
}
/**
 * 遍历reducers中的reducer,检查reducer是否是符合redux规范的reducer。
 * 使用ActionTypes.INIT和随机type生成的action作为第二个参数,分别进行检验
 * 如果返回的state为undefined,则不符合redux规范。
 * @param {Object} reducers 
 */
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.`
      )
    }

    const type = '@@redux/PROBE_UNKNOWN_ACTION_' + Math.random().toString(36).substring(7).split('').join('.')
    if (typeof reducer(undefined, { type }) === '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.`
      )
    }
  })
}


export default function combineReducers(reducers) {
  //第一次筛选,将reducers中不是function 的键值对给筛选掉。
  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]
    }
  }
  //第二次筛选,检测`finalReducers`中是否有不符合`redux`规范的`reducer`
  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) {
    //如果刚才检测 finalReducers 发现了错误,则抛出错误。
    if (shapeAssertionError) {
      throw shapeAssertionError
    }
    //如果不是production环境则抛出warning
    if (process.env.NODE_ENV !== 'production') {
      const warningMessage = getUnexpectedStateShapeWarningMessage(state, finalReducers, action, unexpectedKeyCache)
      if (warningMessage) {
        warning(warningMessage)
      }
    }

    let hasChanged = false
    const nextState = {}
    //遍历所有的reducer,分别执行,将其计算出的state组合起来生成一个大的state.
    // 所以,任何action,redux都会遍历所有的reducer.
    for (let i = 0; i < finalReducerKeys.length; i++) {
      const key = finalReducerKeys[i]
      const reducer = finalReducers[key]
      //为每一个reducer计算一个state.
      const previousStateForKey = state[key]
      const nextStateForKey = reducer(previousStateForKey, action)
      //如果计算出来的state有undefined,抛出错误.
      if (typeof nextStateForKey === 'undefined') {
        const errorMessage = getUndefinedStateErrorMessage(key, action)
        throw new Error(errorMessage)
      }
      //将每一个reducer计算出来的state合并成一个大的state.
      nextState[key] = nextStateForKey
      //只要有一个reducer计算出来的state和之前的不一样,就表明状态树改变了。
      hasChanged = hasChanged || nextStateForKey !== previousStateForKey
    }

    return hasChanged ? nextState : state
  }
}

combineReducer的代码其实还是很简单。首先它会经过两次筛选,第一次筛选将reducersvalue值不是function的键值对都剔除掉,第二次筛选将reducers中不符合redux规范的reducer给筛选掉。

那么,什么是符合redux规范的reducer呢?
我们来看一下assertReducerShape函数:
遍历reducers中的reducer,检查reducer是否是符合redux规范的reducer。
使用ActionTypes.INIT和随机type生成的action作为第二个参数,分别进行检验
如果返回的state为undefined,则不符合redux规范。

接下来设置一个flag叫做hasChanged,默认是false。然后遍历reducers中的所有reducer,分别计算这些子reducer,并将其返回的子state结合成一个大的state。比较计算出来的子state与计算之前的子state是否相同,如果不同,则将hasChanged设为true
hasChanged = hasChanged || nextStateForKey !== previousStateForKey
只要有一个reducer计算出来的state和之前的不一样,就表明状态树改变了

最后,通过判断hasChanged是否变化,返回nextStatestate

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值