if (typeof initialState === 'undefined') { thrownewError( `The slice reducer for key "${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 ( typeofreducer(undefined, { type: ActionTypes.PROBE_UNKNOWN_ACTION(), }) === 'undefined' ) { thrownewError( `The slice reducer for key "${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.` ) } }) }
將參數遍歷一次,把裡面將每個 reducer 的 state 參數都帶入 undefined 與 action 參數帶入包含亂數的 type 的物件並呼叫兩次,這樣做確認 user 的每個 reducer slice 都有正確帶入預設的 state 值,並且即便遇到未知的 action type 也能返回一個 state。
// This is used to make sure we don't warn about the same // keys multiple times. let unexpectedKeyCache if (process.env.NODE_ENV !== 'production') { unexpectedKeyCache = {} }
functiongetUnexpectedStateShapeWarningMessage( 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 "${kindOf( inputState )}". Expected argument to be an object with the following ` + `keys: "${reducerKeys.join('", "')}"` ) }
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.` ) } }
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 actionType = action && action.type thrownewError( `When called with an action of type ${ actionType ? `"${String(actionType)}"` : '(unknown type)' }, the slice reducer for key "${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.` ) } nextState[key] = nextStateForKey hasChanged = hasChanged || nextStateForKey !== previousStateForKey } hasChanged = hasChanged || finalReducerKeys.length !== Object.keys(state).length return hasChanged ? nextState : state }
先說明一下變數的意義
hasChange 是紀錄前後狀態是否相等的 flag
previousStateForKey 是指呼叫對應的 reducer slice 之前的 state slice
nextStateForKey 是指呼叫對應的 reducer slice 之後的 state slice
nextState 是一個收集所有 nextStateForKey 的值的物件
這段主要在做的事情是,迴圈呼叫每個 reducer slice,並且去比較呼叫前後的 state slice 是否有不同,只要有任何一個 slice 結果不同則 hasChanged 會改為 true,代表最後必須要返回 nextState ,相反地如果迴圈跑完沒有發現不同的話,則返回原本的 state 就好。
值得注意的地方是,比較的方法是用 nextStateForKey !== previousStateForKey 來確認,這也是為什麼 Redux 強調不應該直接在 reducer 內 mutate state,因為這樣會比較不出 state 的差異而永遠返回舊的 state 造成沒有渲染的問題。
這是一個為了增進效能帶來的限制,如果遞迴比較每一層的話當然就不用在意 immutable 的問題,但試想每次呼叫 dispatch 時都要將所有 reducer slice 跑一次並且遞迴比較所有的 state slice,那會將時間複雜度(效能)大大的提高啊~
小結
在探討 combineReducers 後知道它做了幾件事情
檢查 user 傳入的 reducer slice 能否在遇到未知的 action type 時以及 state 為 undefined 時仍正常運作
確保初始化及後續呼叫 dispatch 時 reducer 都能正常運作
返回 rootReducer,在每次 dispatch 呼叫時比對前後狀態是否相等,若不相等才會返回新的 state