Redux源码分析(二):combineReducers.js和bindActionCreators.js

前言:本文采用代码注释和具体说明的形式解读源码。

承接上文,我们将在本文对combineReducers.js和bindActionCreators.js进行深入分析,从而完成对Redux源码的分析。

combineReducers.js

combineReducers函数的作用是,把一个由多个不同子reducer函数作为value的object,最终合并成一个reducer 函数,然后就可以对这个reducer调用createStore。

异常处理函数

我们首先来看combineReducers.js里面一些异常处理函数的定义:

1
2
3
4
5
6
7
8
9
10
function getUndefinedStateErrorMessage(key, action) {
const actionType = action && action.type
const actionName = (actionType && `"${actionType.toString()}"`) || 'an action' //获取action的type
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.`
)
}

getUndefinedStateErrorMessage函数主要作用将用于提示,当返回的state为undefined的时候,将调用该函数给开发者错误提示。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
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'
//通过reducer.length,从而判断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.'
)
}
//判断旧的inputState是否是一个object
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('", "')}"`
)
}
//对inputState进行筛选,如果当前inputState的key不在reducers和unexpectedKeyCache中,将其赋值给unexpectedKeys
const unexpectedKeys = Object.keys(inputState).filter(key =>
!reducers.hasOwnProperty(key) &&
!unexpectedKeyCache[key]
)
//将unexpectedKeys中的key赋值给unexpectedKeyCache
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.`
)
}
}

getUnexpectedStateShapeWarningMessage函数主要作用就是检测inputState当中和reducer异常的地方,同时作出错误提示。

接下来的函数是对reducer做有效性验证:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
function assertReducerSanity(reducers) {
Object.keys(reducers).forEach(key => {
const reducer = reducers[key]
const initialState = reducer(undefined, { type: ActionTypes.INIT })
//在初始化阶段,reducer返回的state值如果为undefined,此时会抛出错误提示
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('.')
//当传入其他未知的actionType时,reducer(state,{type})返回的不能是undefined,应该返回原值
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.`
)
}
})
}

assertReducerSanity函数主要是对reducer合法性进行验证。assertReducerSanity函数主要验证初始化返回的state是否为undefined和传入未知action时是否默认返回原state。

combineReducers核心函数

接下来是核心函数combineReducers:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
export default function combineReducers(reducers) {
const reducerKeys = Object.keys(reducers)//取出reducer的各个key值
const finalReducers = {}
for (let i = 0; i < reducerKeys.length; i++) {
const key = reducerKeys[i]//获得对应索引的key值
if (NODE_ENV !== 'production') {
if (typeof reducers[key] === 'undefined') {
warning(`No reducer provided for key "${key}"`)
}
}//非生产环境时,判断当前key对应的子reducer为undefined
if (typeof reducers[key] === 'function') {
finalReducers[key] = reducers[key]
}//将当前key对应的reducer赋值给finalReducers
}
const finalReducerKeys = Object.keys(finalReducers)//获得拷贝reducers的新reducer
//初始化异常keyCache
let unexpectedKeyCache
if (NODE_ENV !== 'production') {
unexpectedKeyCache = {}
}
//对各个子reducer进行格式及合法性判断
let sanityError
try {
assertReducerSanity(finalReducers)
} catch (e) {
sanityError = e
}
return function combination(state = {}, action) {
if (sanityError) {
throw sanityError
}//抛出异常
if (NODE_ENV !== 'production') {
const warningMessage = getUnexpectedStateShapeWarningMessage(state, finalReducers, action, unexpectedKeyCache)
if (warningMessage) {
warning(warningMessage)
}
}//对state进行判断是否和reducer异常冲突
let hasChanged = false //判断是否变化的标记变量
const nextState = {}
for (let i = 0; i < finalReducerKeys.length; i++) {
const key = finalReducerKeys[i] //i索引对应的key
const reducer = finalReducers[key] //对应的子reducer
const previousStateForKey = state[key] //旧的子state
const nextStateForKey = reducer(previousStateForKey, action) //获取子reducer的返回的新的state
if (typeof nextStateForKey === 'undefined') {
const errorMessage = getUndefinedStateErrorMessage(key, action)
throw new Error(errorMessage)
} //如果返回的新的state为undefined,抛出错误。
nextState[key] = nextStateForKey //将新的state赋值给nextState对应的key
hasChanged = hasChanged || nextStateForKey !== previousStateForKey //对nextStateForKey和previousStateForKey进行浅比较,从而更新hasChanged变量
}
return hasChanged ? nextState : state //通过判断hasChanged来确定是返回nextState还是state
}
}

这段代码当中体现了Redux作者很多函数式编程思想。

首先就是对传入的reducer进行复制,避免更改传入的reducer的可能性,体现了函数式编程思想当中纯函数的特性(不改变输入值)。

其次在hasChanged变量的更新当中,只对旧的state和新的state进行浅比较,如果子reducer在定义阶段中,开发者直接在旧的state上修改state,也就是不改变state的引用,那么combineReducers中,对应的子reducer即便被触发reducer修改旧的state,hasChanged将不发生任何改变,因此要求开发者编写的reducer必须是纯函数,不对传入的旧state变量做任何修改。具体有关这点可以查看我之前的博客:[译]为什么Redux需要reducers是纯函数

bindActionCreators.js

bindActionCreators.js文件主要作用就是对store.dispatch和actionCreator进行绑定,不用每次调用手动绑定dispatch和actionCreator。

首先声明一个绑定函数:

1
2
3
function bindActionCreator(actionCreator, dispatch) {
return (...args) => dispatch(actionCreator(...args))
}

接下来是暴露的接口bindActionCreators的函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
export default function bindActionCreators(actionCreators, dispatch) {
if (typeof actionCreators === 'function') {
return bindActionCreator(actionCreators, dispatch)
} //判断actionCreators是否是function类型,是的话,直接调用绑定函数进行绑定返回
if (typeof actionCreators !== 'object' || actionCreators === null) {
throw new Error(
`bindActionCreators expected an object or a function, instead received ${actionCreators === null ? 'null' : typeof actionCreators}. ` +
`Did you write "import ActionCreators from" instead of "import * as ActionCreators from"?`
)
} //如果actionCreators是非object类型或者为null,进行异常抛出
const keys = Object.keys(actionCreators) //取出actionCreators的key数组
const boundActionCreators = {}
for (let i = 0; i < keys.length; i++) {
const key = keys[i]
const actionCreator = actionCreators[key]
if (typeof actionCreator === 'function') {
boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
//通过循环将数组当中各个actionCreator调用bindActionCreator进行绑定
}
}
return boundActionCreators
}

bindActionCreators通过调用bindActionCreator函数实现了store.dispatch和actionCreator的绑定。开发者值需要直接调用bindActionCreators进行绑定,然后就可以直接通过调用绑定的值进行dispatch(action(args)))操作而不需要每次手动声明。

完结

通过这两篇源码阅读,可以对Redux的机制有更深入了解,在使用Redux开发过程中避免不必要的错误,提高开发效率,同时对Redux当中蕴含编程思想有所了解和学习。