随着应用越来越复杂,我们会将多个reducer拆分成多个独立的函数,拆分后每个函数负责独立管理state的一部分。

combineReducers辅助函数的作用,就是把多个不同的reducer函数作用 value 的 object,合并成一个最终的reducer函数,然后对这个reducer调用 createStore方法。

合并后的 reducer 可以调用各个子 reducer,并把它们返回的结果合并成一个 state 对象。 由 combineReducers() 返回的 state 对象,会将传入的每个 reducer 返回的 state 按其传递给 combineReducers() 时对应的 key 进行命名。

示例:

  1. // 创建我们的 reducers。
  2. const _reducers = {
  3. items(items = [], { type, payload }) {
  4. if (type === 'ADD_ITEMS') items.push(payload);
  5. return items;
  6. },
  7. isLoading(isLoading = false, { type, payload }) {
  8. if (type === 'IS_LOADING') return true;
  9. return false;
  10. }
  11. };
  12. // 创建根 rootReducer
  13. const rootReducer = combineReducers(_reducers)
  14. // createStore 接受 reducers,创建我们需要的 store。
  15. const store = createStore(rootReducer);
  16. // 拿到组合后的state
  17. const state = store.getState();

rootReducer 返回一个函数 combination(state = {}, action),再交给 createStore来创建我们需要的 store
带着示例我们来看源码:

源码

先看辅助函数:

  1. getUndefinedStateErrorMessage,如果一个reducer返回的是一个undefined的state,就会调用

    1. function getUndefinedStateErrorMessage(key, action) {
    2. const actionType = action && action.type
    3. const actionDescription =
    4. (actionType && `action "${String(actionType)}"`) || 'an action'
    5. return (
    6. `Given ${actionDescription}, reducer "${key}" returned undefined. ` +
    7. `To ignore an action, you must explicitly return the previous state. ` +
    8. `If you want this reducer to hold no value, you can return null instead of undefined.`
    9. )
    10. }
  2. getUnexpectedStateShapeWarningMessage,用于校验未知键、不是对象的state,对于REPLACE类型,不校验。

    1. function getUnexpectedStateShapeWarningMessage(
    2. inputState,
    3. reducers,
    4. action,
    5. unexpectedKeyCache
    6. ) {
    7. const reducerKeys = Object.keys(reducers)
    8. const argumentName =
    9. action && action.type === ActionTypes.INIT
    10. ? 'preloadedState argument passed to createStore'
    11. : 'previous state received by the reducer'
    12. if (reducerKeys.length === 0) {
    13. return (
    14. 'Store does not have a valid reducer. Make sure the argument passed ' +
    15. 'to combineReducers is an object whose values are reducers.'
    16. )
    17. }
    18. if (!isPlainObject(inputState)) {
    19. return (
    20. `The ${argumentName} has unexpected type of "` +
    21. {}.toString.call(inputState).match(/\s([a-z|A-Z]+)/)[1] +
    22. `". Expected argument to be an object with the following ` +
    23. `keys: "${reducerKeys.join('", "')}"`
    24. )
    25. }
    26. const unexpectedKeys = Object.keys(inputState).filter(
    27. key => !reducers.hasOwnProperty(key) && !unexpectedKeyCache[key]
    28. )
    29. unexpectedKeys.forEach(key => {
    30. unexpectedKeyCache[key] = true
    31. })
    32. if (action && action.type === ActionTypes.REPLACE) return
    33. if (unexpectedKeys.length > 0) {
    34. return (
    35. `Unexpected ${unexpectedKeys.length > 1 ? 'keys' : 'key'} ` +
    36. `"${unexpectedKeys.join('", "')}" found in ${argumentName}. ` +
    37. `Expected to find one of the known reducer keys instead: ` +
    38. `"${reducerKeys.join('", "')}". Unexpected keys will be ignored.`
    39. )
    40. }
    41. }
  3. assertReducerShape,校验reducer的合理性,初始化和返回值不能是undefined

    1. function assertReducerShape(reducers) {
    2. Object.keys(reducers).forEach(key => {
    3. const reducer = reducers[key]
    4. const initialState = reducer(undefined, { type: ActionTypes.INIT })
    5. if (typeof initialState === 'undefined') {
    6. throw new Error(
    7. `Reducer "${key}" returned undefined during initialization. ` +
    8. `If the state passed to the reducer is undefined, you must ` +
    9. `explicitly return the initial state. The initial state may ` +
    10. `not be undefined. If you don't want to set a value for this reducer, ` +
    11. `you can use null instead of undefined.`
    12. )
    13. }
    14. if (
    15. typeof reducer(undefined, {
    16. type: ActionTypes.PROBE_UNKNOWN_ACTION()
    17. }) === 'undefined'
    18. ) {
    19. throw new Error(
    20. `Reducer "${key}" returned undefined when probed with a random type. ` +
    21. `Don't try to handle ${ActionTypes.INIT} or other actions in "redux/*" ` +
    22. `namespace. They are considered private. Instead, you must return the ` +
    23. `current state for any unknown actions, unless it is undefined, ` +
    24. `in which case you must return the initial state, regardless of the ` +
    25. `action type. The initial state may not be undefined, but can be null.`
    26. )
    27. }
    28. })
    29. }

主入口

combineReducers 函数的主入口:
接收参数 reducers,是一个对象。

  1. function combineReducers(reducers) {
  2. const reducerKeys = Object.keys(reducers)
  3. const finalReducers = {}
  4. for (let i = 0; i < reducerKeys.length; i++) {
  5. const key = reducerKeys[i]
  6. if (process.env.NODE_ENV !== 'production') {
  7. if (typeof reducers[key] === 'undefined') {
  8. warning(`No reducer provided for key "${key}"`)
  9. }
  10. }
  11. // reducers只能是函数, 可以排除掉不正确的
  12. if (typeof reducers[key] === 'function') {
  13. finalReducers[key] = reducers[key]
  14. }
  15. }
  16. // 最终的 Reducers
  17. const finalReducerKeys = Object.keys(finalReducers)
  18. // This is used to make sure we don't warn about the same
  19. // keys multiple times.
  20. let unexpectedKeyCache
  21. if (process.env.NODE_ENV !== 'production') {
  22. unexpectedKeyCache = {}
  23. }
  24. let shapeAssertionError
  25. try {
  26. // 校验所有 reducers 的合理性
  27. assertReducerShape(finalReducers)
  28. } catch (e) {
  29. shapeAssertionError = e
  30. }
  31. // 返回一个新的 reducer,每次dispatch一个action,都会执行一遍这里。
  32. return function combination(state = {}, action) {
  33. if (shapeAssertionError) {
  34. throw shapeAssertionError
  35. }
  36. if (process.env.NODE_ENV !== 'production') {
  37. // 非生产环境,校验state
  38. const warningMessage = getUnexpectedStateShapeWarningMessage(
  39. state,
  40. finalReducers,
  41. action,
  42. unexpectedKeyCache
  43. )
  44. if (warningMessage) {
  45. warning(warningMessage)
  46. }
  47. }
  48. // 是否已改变
  49. let hasChanged = false
  50. // state 是否改变的标志位。
  51. const nextState = {}
  52. // 遍历所有reducers
  53. for (let i = 0; i < finalReducerKeys.length; i++) {
  54. const key = finalReducerKeys[i]
  55. const reducer = finalReducers[key]
  56. const previousStateForKey = state[key] // 上一个state,也就是旧的state
  57. const nextStateForKey = reducer(previousStateForKey, action) // 执行reducer,拿到新的state值
  58. if (typeof nextStateForKey === 'undefined') {
  59. const errorMessage = getUndefinedStateErrorMessage(key, action)
  60. throw new Error(errorMessage)
  61. }
  62. nextState[key] = nextStateForKey // 返回值赋值给nextState
  63. hasChanged = hasChanged || nextStateForKey !== previousStateForKey // 判断state是否发生了变化
  64. }
  65. hasChanged =
  66. hasChanged || finalReducerKeys.length !== Object.keys(state).length
  67. return hasChanged ? nextState : state // state有变化就返回新的state
  68. }
  69. }

总结

combineReducers函数的作用就是将多个reducer整合起来,交给createStore创建一个新的reducers

可以这样理解 combineReducers 就是一个高阶的 reducer方法,因为其返回函数 combination 方法接收的参数是 state和action,其返回值也是一个 state。

在每次 dispatch 时都会执行 combination 函数遍历所有reducers,再调用各自的 reducer