Projekt

Obecné

Profil

Stáhnout (5.96 KB) Statistiky
| Větev: | Revize:
1 3a515b92 cagy
import ActionTypes from './utils/actionTypes'
2
import warning from './utils/warning'
3
import isPlainObject from './utils/isPlainObject'
4
5
function getUndefinedStateErrorMessage(key, action) {
6
  const actionType = action && action.type
7
  const actionDescription =
8
    (actionType && `action "${String(actionType)}"`) || 'an action'
9
10
  return (
11
    `Given ${actionDescription}, reducer "${key}" returned undefined. ` +
12
    `To ignore an action, you must explicitly return the previous state. ` +
13
    `If you want this reducer to hold no value, you can return null instead of undefined.`
14
  )
15
}
16
17
function getUnexpectedStateShapeWarningMessage(
18
  inputState,
19
  reducers,
20
  action,
21
  unexpectedKeyCache
22
) {
23
  const reducerKeys = Object.keys(reducers)
24
  const argumentName =
25
    action && action.type === ActionTypes.INIT
26
      ? 'preloadedState argument passed to createStore'
27
      : 'previous state received by the reducer'
28
29
  if (reducerKeys.length === 0) {
30
    return (
31
      'Store does not have a valid reducer. Make sure the argument passed ' +
32
      'to combineReducers is an object whose values are reducers.'
33
    )
34
  }
35
36
  if (!isPlainObject(inputState)) {
37
    return (
38
      `The ${argumentName} has unexpected type of "` +
39
      {}.toString.call(inputState).match(/\s([a-z|A-Z]+)/)[1] +
40
      `". Expected argument to be an object with the following ` +
41
      `keys: "${reducerKeys.join('", "')}"`
42
    )
43
  }
44
45
  const unexpectedKeys = Object.keys(inputState).filter(
46
    key => !reducers.hasOwnProperty(key) && !unexpectedKeyCache[key]
47
  )
48
49
  unexpectedKeys.forEach(key => {
50
    unexpectedKeyCache[key] = true
51
  })
52
53
  if (action && action.type === ActionTypes.REPLACE) return
54
55
  if (unexpectedKeys.length > 0) {
56
    return (
57
      `Unexpected ${unexpectedKeys.length > 1 ? 'keys' : 'key'} ` +
58
      `"${unexpectedKeys.join('", "')}" found in ${argumentName}. ` +
59
      `Expected to find one of the known reducer keys instead: ` +
60
      `"${reducerKeys.join('", "')}". Unexpected keys will be ignored.`
61
    )
62
  }
63
}
64
65
function assertReducerShape(reducers) {
66
  Object.keys(reducers).forEach(key => {
67
    const reducer = reducers[key]
68
    const initialState = reducer(undefined, { type: ActionTypes.INIT })
69
70
    if (typeof initialState === 'undefined') {
71
      throw new Error(
72
        `Reducer "${key}" returned undefined during initialization. ` +
73
          `If the state passed to the reducer is undefined, you must ` +
74
          `explicitly return the initial state. The initial state may ` +
75
          `not be undefined. If you don't want to set a value for this reducer, ` +
76
          `you can use null instead of undefined.`
77
      )
78
    }
79
80
    if (
81
      typeof reducer(undefined, {
82
        type: ActionTypes.PROBE_UNKNOWN_ACTION()
83
      }) === 'undefined'
84
    ) {
85
      throw new Error(
86
        `Reducer "${key}" returned undefined when probed with a random type. ` +
87
          `Don't try to handle ${ActionTypes.INIT} or other actions in "redux/*" ` +
88
          `namespace. They are considered private. Instead, you must return the ` +
89
          `current state for any unknown actions, unless it is undefined, ` +
90
          `in which case you must return the initial state, regardless of the ` +
91
          `action type. The initial state may not be undefined, but can be null.`
92
      )
93
    }
94
  })
95
}
96
97
/**
98
 * Turns an object whose values are different reducer functions, into a single
99
 * reducer function. It will call every child reducer, and gather their results
100
 * into a single state object, whose keys correspond to the keys of the passed
101
 * reducer functions.
102
 *
103
 * @param {Object} reducers An object whose values correspond to different
104
 * reducer functions that need to be combined into one. One handy way to obtain
105
 * it is to use ES6 `import * as reducers` syntax. The reducers may never return
106
 * undefined for any action. Instead, they should return their initial state
107
 * if the state passed to them was undefined, and the current state for any
108
 * unrecognized action.
109
 *
110
 * @returns {Function} A reducer function that invokes every reducer inside the
111
 * passed object, and builds a state object with the same shape.
112
 */
113
export default function combineReducers(reducers) {
114
  const reducerKeys = Object.keys(reducers)
115
  const finalReducers = {}
116
  for (let i = 0; i < reducerKeys.length; i++) {
117
    const key = reducerKeys[i]
118
119
    if (process.env.NODE_ENV !== 'production') {
120
      if (typeof reducers[key] === 'undefined') {
121
        warning(`No reducer provided for key "${key}"`)
122
      }
123
    }
124
125
    if (typeof reducers[key] === 'function') {
126
      finalReducers[key] = reducers[key]
127
    }
128
  }
129
  const finalReducerKeys = Object.keys(finalReducers)
130
131
  // This is used to make sure we don't warn about the same
132
  // keys multiple times.
133
  let unexpectedKeyCache
134
  if (process.env.NODE_ENV !== 'production') {
135
    unexpectedKeyCache = {}
136
  }
137
138
  let shapeAssertionError
139
  try {
140
    assertReducerShape(finalReducers)
141
  } catch (e) {
142
    shapeAssertionError = e
143
  }
144
145
  return function combination(state = {}, action) {
146
    if (shapeAssertionError) {
147
      throw shapeAssertionError
148
    }
149
150
    if (process.env.NODE_ENV !== 'production') {
151
      const warningMessage = getUnexpectedStateShapeWarningMessage(
152
        state,
153
        finalReducers,
154
        action,
155
        unexpectedKeyCache
156
      )
157
      if (warningMessage) {
158
        warning(warningMessage)
159
      }
160
    }
161
162
    let hasChanged = false
163
    const nextState = {}
164
    for (let i = 0; i < finalReducerKeys.length; i++) {
165
      const key = finalReducerKeys[i]
166
      const reducer = finalReducers[key]
167
      const previousStateForKey = state[key]
168
      const nextStateForKey = reducer(previousStateForKey, action)
169
      if (typeof nextStateForKey === 'undefined') {
170
        const errorMessage = getUndefinedStateErrorMessage(key, action)
171
        throw new Error(errorMessage)
172
      }
173
      nextState[key] = nextStateForKey
174
      hasChanged = hasChanged || nextStateForKey !== previousStateForKey
175
    }
176
    hasChanged =
177
      hasChanged || finalReducerKeys.length !== Object.keys(state).length
178
    return hasChanged ? nextState : state
179
  }
180
}