import ActionTypes
from './utils/actionTypes'
import warning
from './utils/warning'
import isPlainObject
from './utils/isPlainObject'
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.`
)
}
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.`
)
}
}
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.`
)
}
})
}
export default function combineReducers(reducers
) {
// 获取每个reducer的参数名
const reducerKeys
= Object
.keys(reducers
)
// 最后返回的包含reducer的对象
const finalReducers
= {}
for (let i
= 0; i
< reducerKeys
.length
; i
++) {
const key
= reducerKeys
[i
]
// reducer不能为undefined
if (process
.env
.NODE_ENV !== 'production') {
if (typeof reducers
[key
] === 'undefined') {
warning(`No reducer provided for key "${key}"`)
}
}
// 如果该对象值为一个函数 把该对象添加到finalReducers中
if (typeof reducers
[key
] === 'function') {
finalReducers
[key
] = reducers
[key
]
}
}
const finalReducerKeys
= Object
.keys(finalReducers
)
// 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
= {}
}
let shapeAssertionError
try {
// 验证reducer的返回值是不是undefined
assertReducerShape(finalReducers
)
} catch (e) {
shapeAssertionError
= e
}
return function combination(state
= {}, action
) {
// 如果有reducer返回值为undefined 直接报错
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
++) {
// reducers对象属性名
const key
= finalReducerKeys
[i
]
// reducer
const reducer
= finalReducers
[key
]
// 获取当前对应reducer的仓库状态state
const previousStateForKey
= state
[key
]
// 调用reducer得到新的仓库状态state
const nextStateForKey
= reducer(previousStateForKey
, action
)
// 如果得到的仓库状态state为undefined报错
if (typeof nextStateForKey
=== 'undefined') {
const errorMessage
= getUndefinedStateErrorMessage(key
, action
)
throw new Error(errorMessage
)
}
// 保存更新后的仓库状态
nextState
[key
] = nextStateForKey
// 通过比较前后两次仓库状态state判断当前的仓库状态有没有改变
hasChanged
= hasChanged
|| nextStateForKey
!== previousStateForKey
}
// 通过仓库状态的对象和reducers的对象长度是否一致 来判断是否修改过
hasChanged
=
hasChanged
|| finalReducerKeys
.length
!== Object
.keys(state
).length
// 判断当前的仓库状态有没有改变 返回新的仓库状态state
return hasChanged
? nextState
: state
}
}