Initializing State: How Redux state is initialized">Initializing State: How Redux state is initialized">
跳至主要内容

初始化状态

有两种主要方法可以为你的应用程序初始化状态。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 仍然需要提供一个默认值来处理传递给它们的 stateundefined 的情况。所有 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!),所以默认参数语法没有效果。**state4242 从 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 初始化为{}。因此,当它调用ab reducer 时,state.astate.b 将是undefined。**ab 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。