初始化 State

主要有两种方法可以用来初始化你的应用的 state 。

  1. 可以通过 createStore 方法,该方法接受可选的 preloadedState 做为其第二个参数;

  2. reducer 中 state 参数的默认值为 undefined ,修改其默认值也可以用来初始化应用的 state,可以在 reducer 中检查 state 值是否为 undefined 从而显式的添加默认值,也可以通过 ES6 中默认参数的语法来添加默认值:function myReducer(state = someDefaultValue, action)

两种初始化 State 的方法会如何相互影响并没有那么直观,不过好在它们相互作用的过程遵守下面的这些明确的规则。

概要

如果没有使用 combineReducers() 或者类似功能的代码,preloadedState 优先级会比在 reducer 里面使用 state = ... 高 ,这是因为 preloadedState 会做为 state 传递到 reducer 中,state 的值不再是 undefined ,ES6 的默认参数不会生效。

如果使用了 combineReducers() 方法,结果就会有一些细微的差别。那些在 preloadedState 中指明了 state 的 reducer 会以对应传入的值做为初始值。其他的 reducer 接收到的则还是 undefined 则还会使用 state = ... 指定的默认值。

通常情况下,通过 preloadedState 指定的 state 优先级要高于通过 reducer 指定的 state。这种机制的存在允许我们在 reducer 可以通过指明默认参数来指定初始数据,而且还为通过服务端或者其它机制注入数据到 store 中提供了可能。

注意:通过 preloadedState 传入了初始化数据的 reducers 仍然需要添加默认值来应对传递的 state 为 undefined 的情况。这样,当所有的 reducers 在初始化时被传入 state 的都是 undefined 时,还可以返回一些默认值,默认值可以是任何非 undefined 的值,不过也没有必要复制 preloadedState 做为其默认值。

深入理解

单个简单的 Reducer

首先我们来考虑只有一个 reducer 的情况,这种情况下我们无须使用 combineReducers() 方法。 此时的 reducer 可能会是下面这个样子:

  1. function counter(state = 0, action) {
  2. switch (action.type) {
  3. case 'INCREMENT':
  4. return state + 1
  5. case 'DECREMENT':
  6. return state - 1
  7. default:
  8. return state
  9. }
  10. }

接下来,假设你通过上面的 reducer 创建了一个 store。

  1. import { createStore } from 'redux'
  2. const store = createStore(counter)
  3. console.log(store.getState()) // 0

结果我们得到的初始的 state 的值为 0 ,为什么呢? 因为你传给 createStore 的第二个参数是 undefined,也就是说你传给 reducer 中的 state 的初始值也是 undefined 。当 Redux 初始化时,实际上会触发一个 “dummy” action 来填充 store。当 reducer counter 被调用时,由于传入的 state 等于 undefined ,默认参数生效了。因此 state 的值为设定的默认值 0。

我们来看看另一种不同的场景:

  1. import { createStore } from 'redux'
  2. const store = createStore(counter, 42)
  3. console.log(store.getState()) // 42

此时得到的值为 42,不是 0,为什么又会这样呢? 这种情况下 createStore 方法被调用时,第二个参数传入的是 42。这个值会在 “dummmy” action 触发时传递给 state,由于这个值不为 undefined,因此设定的默认值不会起作用,在 reducer 中 state 返回值 42。

合并的 Reducers

我们再来看看使用了 combineReducers() 方法的情况。 假设你有下面这两个 reducer:

  1. function a(state = 'lol', action) {
  2. return state;
  3. }
  4. function b(state = 'wat', action) {
  5. return state;
  6. }

通过 combineReducers({ a, b }) 方式我们可以把上面两个 reducer 合并为一个 reducer

  1. // const combined = combineReducers({ a, b })
  2. function combined(state = {}, action) {
  3. return {
  4. a: a(state.a, action),
  5. b: b(state.b, action)
  6. }
  7. }

如果我们调用 createStore 方法时,不传入 preloadedState ,初始化 state 会为 {}。在调用对应的 reducer 时会传入 state.astate.b 做为 state 的参数,不过由于这两个值都是 undefined。a 和 b 两个 reducer 都会接收 undefined 作为 state 的参数,此时如果 state 有默认值,就会返回默认值。因此上述组合 reducer 在首次调用时会返回 { a: 'lol', b: 'wat' }

  1. import { createStore } from 'redux'
  2. const store = createStore(combined)
  3. console.log(store.getState()) // { a: 'lol', b: 'wat' }

我们再来看看一个不同的情况:

  1. import { createStore } from 'redux'
  2. const store = createStore(combined, { a: 'horse' })
  3. console.log(store.getState()) // { a: 'horse', b: 'wat' }

上述代码中我在 createStore() 方法调用时传入了 preloadedState 。得到的结果是我为 a reducer 指定的值和 b reducer 的默认值组合而成。

我们仔细来看看这个组合 reducer 做了什么:

  1. // const combined = combineReducers({ a, b })
  2. function combined(state = {}, action) {
  3. return {
  4. a: a(state.a, action),
  5. b: b(state.b, action)
  6. }
  7. }

上面的代码中,指定了组合 reducer 的 state ,因此其值不会是默认的 {}, 而是一个对象,并且该对象中键 a 的值为 ’horse',但是并不存在键 b。这就导致 a reducer 接受 horse 作为其 state 的参数,而 b reducer 则接收 undefined 做为其 state 的参数,因此在 b reducer 中设置的默认 state 会生效 (在本例中是 'wat')。最终我们得到的结果为 { a: 'horse', b: 'wat'}

总结

总结一下,如果你使用 redux 的推荐做法,在 reducer 中给定 state 参数的默认值(最简单的方法是通过 ES6 的默认值语法),你将拥有一个表现良好的组合 reducer。这个组合 reducer 会优先使用你通过 preloadedState 传递来的对应的值,不过就算你没传递或者不存在对应的值,也会使用你设定的默认值。这种机制非常棒,它提供了设置初始值并注入的途径,也保留了 reducer 设置默认值的能力。加上combineReducers() 可以在不同级别上使用,这种模式可以递归的使用。