本页面由 PageTurner AI 翻译(测试版)。未经项目官方认可。 发现错误? 报告问题 →
初始化状态
为应用程序初始化状态主要有两种方式。createStore 方法可接受可选的 preloadedState 值作为其第二个参数。Reducers 也可以通过检测传入的 state 参数是否为 undefined 来指定初始值,并返回默认值。这可以通过在 reducer 内显式检查实现,或使用默认参数语法:function myReducer(state = someDefaultValue, action)。
这两种方式的交互机制并不总是显而易见。幸运的是,该过程遵循可预测的规则。以下是它们协同工作的原理。
核心要点
在没有 combineReducers() 或类似手动代码的情况下,preloadedState 总是优先于 reducer 中的 state = ...,因为传给 reducer 的 state 就是 preloadedState 且不为 undefined,因此默认参数语法不会生效。
使用 combineReducers() 时行为更为微妙。状态在 preloadedState 中指定的 reducers 将接收该状态。其他 reducers 将收到 undefined,因此会回退到它们指定的 state = ... 默认参数。
总体而言,preloadedState 优先级高于 reducer 指定的状态。这使得 reducers 可以设置符合自身逻辑的默认参数,同时支持从持久化存储或服务器注水( hydrate )存储时加载现有数据(完整或部分)。
注意:使用 preloadedState 初始化状态的 reducers 仍需提供默认值来处理传入 state 为 undefined 的情况。所有 reducers 初始化时都会收到 undefined,因此应确保传入 undefined 时返回有效值。可以是任意非 undefined 值;无需在此处重复 preloadedState 的内容作为默认值。
深入解析
单一简单 Reducer
首先考虑使用单个 reducer 的情况。假设不使用 combineReducers()。
此时 reducer 可能如下所示:
function counter(state = 0, action) {
switch (action.type) {
case 'INCREMENT':
return state + 1
case 'DECREMENT':
return state - 1
default:
return state
}
}
现在创建对应的 store:
import { createStore } from 'redux'
const store = createStore(counter)
console.log(store.getState()) // 0
初始状态为何是零?因为 createStore 的第二个参数是 undefined。这是首次调用 reducer 时传入的 state。Redux 初始化时会分发"虚拟" action 来填充状态。因此 counter reducer 被调用时 state 为 undefined。这正触发了默认参数机制。因此,state 现在是 0,这是根据默认的 state 值(state = 0)。这个状态(0)将被返回。
现在考虑另一种场景:
import { createStore } from 'redux'
const store = createStore(counter, 42)
console.log(store.getState()) // 42
为何是 42 而非 0?因为 createStore 的第二个参数是 42。该参数成为分发虚拟 action 时传入 reducer 的 state。此时 state 非 undefined(它是 42!),因此默认参数语法不生效。state 值为 42,并且 42 被直接返回。
组合 Reducers
现在考虑使用 combineReducers() 的情况。有两个 reducers:
function a(state = 'lol', action) {
return state
}
function b(state = 'wat', action) {
return state
}
由 combineReducers({ a, b }) 生成的 reducer 如下:
// const combined = combineReducers({ a, b })
function combined(state = {}, action) {
return {
a: a(state.a, action),
b: b(state.b, action)
}
}
若调用 createStore 时不传入 preloadedState,初始 state 将被设为空对象 {}。因此当调用 a 和 b 这两个 reducer 时,state.a 与 state.b 的值均为 undefined。a 和 b 这两个 reducer 收到的 state 参数都是 undefined,此时若它们定义了默认 state 值,就会返回这些默认值。这就是首次调用组合 reducer 时会返回 { a: 'lol', b: 'wat' } 状态对象的原因。
import { createStore } from 'redux'
const store = createStore(combined)
console.log(store.getState()) // { a: 'lol', b: 'wat' }
现在考虑另一种场景:
import { createStore } from 'redux'
const store = createStore(combined, { a: 'horse' })
console.log(store.getState()) // { a: 'horse', b: 'wat' }
此时向 createStore() 传入了 preloadedState 参数。组合 reducer 返回的状态将合并:a reducer 使用我指定的初始状态值,而 b reducer 则采用自身通过默认参数定义的 'wat' 值。
让我们回顾组合 reducer 的工作原理:
// const combined = combineReducers({ a, b })
function combined(state = {}, action) {
return {
a: a(state.a, action),
b: b(state.b, action)
}
}
本例中,由于指定了 state,因此不会回退到 {} 默认值。该对象包含值为 'horse' 的 a 字段,但缺少 b 字段。因此 a reducer 收到 'horse' 作为其 state 并直接返回,而 b reducer 收到 undefined 作为其 state,于是返回 它自己定义的 默认 state(本例中为 'wat')。最终我们得到返回状态 { a: 'horse', b: 'wat' }。
重点回顾
总结来说,若遵循 Redux 约定:当 reducer 的 state 参数为 undefined 时返回初始状态(最简单的实现方式是通过 state 默认参数),组合 reducer 将产生符合预期的行为。它们会优先采用传入 createStore() 的 preloadedState 对象中的对应值;若未传入该参数,或对应字段未设置,则使用 reducer 自身指定的默认 state 值。 这种机制既能实现初始化,又能加载已有数据,同时允许单个 reducer 在数据未被保留时重置状态。当然,此模式可递归应用——你可以在多层嵌套中使用 combineReducers(),甚至通过手动调用 reducer 并传入状态树相关部分来实现自定义组合。