初始化状态
有两种主要方法可以为你的应用程序初始化状态。createStore
方法可以接受一个可选的 preloadedState
值作为它的第二个参数。Reducer 也可以通过查找传入的 undefined
状态参数来指定初始值,并返回它们想要用作默认值的 value。这可以通过在 reducer 中进行显式检查来完成,或者使用默认参数值语法:function myReducer(state = someDefaultValue, action)
。
这两种方法的交互方式并不总是很清楚。幸运的是,这个过程遵循一些可预测的规则。以下是各部分如何组合在一起的。
总结
如果没有 combineReducers()
或类似的手动代码,preloadedState
总是会胜过 reducer 中的 state = ...
,因为传递给 reducer 的 state
是 preloadedState
而不是 undefined
,所以参数语法不适用。
使用 combineReducers()
时,行为会更加细致。那些在 preloadedState
中指定了状态的 reducer 会收到该状态。其他 reducer 会收到 undefined
,因此会回退到他们指定的 state = ...
默认参数。
一般来说,preloadedState
会优先于 reducer 指定的状态。这允许 reducer 指定对它们来说有意义的初始数据作为默认参数,但也允许在从持久存储或服务器中恢复状态时加载现有数据(全部或部分)。
注意:使用 preloadedState
填充初始状态的 reducer 仍然需要提供一个默认值来处理传递给它们的 state
为 undefined
的情况。所有 reducer 在初始化时都会传递 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 初始化时,它会分派一个“虚拟”动作来填充状态。因此,你的 counter
reducer 被调用,state
等于 undefined
。这正是“激活”默认参数的情况。 因此,state
现在根据默认 state
值 (state = 0
) 为 0
。此状态 (0
) 将被返回。
让我们考虑另一种情况
import { createStore } from 'redux'
const store = createStore(counter, 42)
console.log(store.getState()) // 42
为什么这次是42
而不是0
?因为createStore
被调用时,第二个参数是42
。这个参数会变成传递给你的 reducer 的state
,以及一个虚拟的 action。**这次,state
不是未定义的(它是42
!),所以默认参数语法没有效果。**state
是42
,42
从 reducer 返回。
组合 Reducer
现在让我们考虑一个你使用combineReducers()
的情况。你有两个 reducer
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 都将接收undefined
作为它们的state
参数,如果它们指定了默认的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' }
现在我将preloadedState
指定为createStore()
的参数。从组合 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
被指定了,所以它没有回退到{}
。它是一个对象,其中a
字段等于'horse'
,但没有b
字段。这就是为什么a
reducer 接收'horse'
作为它的state
并愉快地返回它,但b
reducer 接收undefined
作为它的state
,因此返回了它对默认state
的想法(在我们的例子中是'wat'
)。这就是我们如何得到{ a: 'horse', b: 'wat' }
的原因。
回顾
总而言之,如果你遵循 Redux 约定,并在 reducer 被调用时使用 `undefined` 作为 `state` 参数返回初始状态(实现这一点最简单的方法是指定 `state` 的默认参数值),那么你将获得组合 reducer 的一个很好的有用行为。**它们会优先选择你传递给 `createStore()` 函数的 `preloadedState` 对象中的对应值,但如果你没有传递任何值,或者对应字段没有设置,则会选择 reducer 指定的默认 `state` 参数。**这种方法效果很好,因为它既提供了初始化,又提供了现有数据的重新水合,但允许单个 reducer 在其数据未保存的情况下重置其状态。当然,你可以递归地应用这种模式,因为你可以在多个级别上使用 `combineReducers()`,甚至可以通过手动调用 reducer 并传递状态树的相关部分来组合 reducer。