事出有因,写 rabbit 的时候,一条单测出了问题

Reducer _object returned undefined during initialization.

想来必然是 reducer 缺少默认的 state 所致,但是我的单测是从 dva 直接拿过来改改就用的,应该不会出现问题,所以一定是哪里出了差错。后面发现了,虽然 dva 可以接受 reducers 是空对象,但应用运行起来有一样问题。

主要是这个问题无伤大雅,model 不会出现只有 reducers 这种情况。我也给出了解决:构建 reducer 给一个默认 state 顶着(但我认为一个 model 应该强制存在 state

显然这次并不想讲这个,而是想理解 combineReducers

combineReducers 一共有 4 个方法:

  • getUndefinedStateErrorMessage
  • getUnexpectedStateShapeWarningMessage
  • assertReducerShape
  • (主要方法) combineReducers

getUndefinedStateErrorMessage

很明显,这是一个生成错误信息的方法。主要是限制 reducer 必须返回 state

assertReducerShape

这是一个检查 reducer 是否合规的方法。

首先接受一个集合所有 reducer 的对象进行遍历,每一个抽出来检验,做一次运行尝试,看是否能得到 state。如果得到的 stateundefined 则该 reducer 是非法的。

文中开头提到的问题正是由这里报出,因为我们给出的 reducers 为空对象,且 stateundefined,所以自然会报错。dva 的单测只检查 model 的合理性,且 state 允许任何内容和 reducers 允许空对象,并没有考虑一个 model 同时不存在这两者的情况,不过显然没意义,所以目前是不清楚 dva 团队是没想到还是也觉得无所谓。

combineReducers

这是这组方法中的核心,该方法也作为默认方法导出。这组方法的目的是把一群 reducer 合并为一个方法供 createStore 使用。

上来是两组变量:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 提取对象中所有字段名,一个 key 对应一个 reducer 方法
const reducerKeys = Object.keys(reducers)
// 由下面可得,该变量用来暂存基本合理(reducer 是一个方法)的 reducer 方法。
const finalReducers = {}

//...
if (typeof reducers[key] === 'function') {
finalReducers[key] = reducers[key]
}
// 如果在非生产环境中,还会提示仅有 key 没有方法的值以方便我们修改调试

// 再把初步验证过的 reducer 取出
const finalReducerKeys = Object.keys(finalReducers)

//...

let shapeAssertionError
// 放入刚刚用来验证 reducer 合规的方法进行第二次验证,并做错误处理
try {
assertReducerShape(finalReducers)
} catch (e) {
shapeAssertionError = e
}

接着是返回一个新的 reducer,并对所有传入的合规的 reducer 进行 diff。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 创建一个对比记录变量以及新 state
let hasChanged = false
const nextState = {}

// 遍历刚刚验证完的 reducer key 数组
for (let i = 0; i < finalReducerKeys.length; i++) {
const key = finalReducerKeys[i]
const reducer = finalReducers[key]
// 获取当前 state
const previousStateForKey = state[key]
// 通过执行 reducer 得到新的 state
const nextStateForKey = reducer(previousStateForKey, action)
if (typeof nextStateForKey === 'undefined') {
const errorMessage = getUndefinedStateErrorMessage(key, action)
throw new Error(errorMessage)
}
// 并将新 reducer 产物绑定到 nextState,key 不变。(方便对比)
nextState[key] = nextStateForKey
// 右式第一个 hasChanged 表达:如果已经为 true 那么就肯定 true,没必要再去对比验证
hasChanged = hasChanged || nextStateForKey !== previousStateForKey
}
// 返回:有改变的返回新的,无改变返回原 state。
return hasChanged ? nextState : state

所以这组方法目的就是把所有复杂的 reducerstate 组成新的状态树,统一管理。业务开发时可以根据需求拆分多个 reducerstate,便于开发和组织。