Skip to main content

初始化状态

¥Initializing State

有两种主要方法可以初始化应用的状态。createStore 方法可以接受可选的 preloadedState 值作为其第二个参数。reducer 还可以通过查找传入的状态参数 undefined 来指定初始值,并返回它们想要用作默认值的值。这可以通过 reducer 内部的显式检查来完成,也可以使用默认参数值语法来完成:function myReducer(state = someDefaultValue, action)

¥There are two main ways to initialize state for your application. The createStore method can accept an optional preloadedState value as its second argument. Reducers can also specify an initial value by looking for an incoming state argument that is undefined, and returning the value they'd like to use as a default. This can either be done with an explicit check inside the reducer, or by using the default argument value syntax: function myReducer(state = someDefaultValue, action).

这两种方法如何相互作用并不总是立即清楚。幸运的是,这个过程确实遵循一些可预测的规则。以下是各个部分如何组合在一起的。

¥It's not always immediately clear how these two approaches interact. Fortunately, the process does follow some predictable rules. Here's how the pieces fit together.

概括

¥Summary

如果没有 combineReducers() 或类似的手动代码,preloadedState 在 reducer 中总是胜过 state = ...,因为传递给 reducer 的 statepreloadedState 而不是 undefined,因此参数语法不适用。

¥Without combineReducers() or similar manual code, preloadedState always wins over state = ... in the reducer because the state passed to the reducer is preloadedState and is not undefined, so the argument syntax doesn't apply.

对于 combineReducers(),行为更加微妙。那些状态在 preloadedState 中指定的 reducer 将接收该状态。其他 reducer 将收到 undefined,因此将回退到它们指定的 state = ... 默认参数。

¥With combineReducers() the behavior is more nuanced. Those reducers whose state is specified in preloadedState will receive that state. Other reducers will receive undefined and because of that will fall back to the state = ... default argument they specify.

一般来说,preloadedState 胜过 reducer 指定的状态。这允许 reducer 指定对它们有意义的初始数据作为默认参数,而且还允许在你从某些持久存储或服务器中加载存储时加载现有数据(全部或部分)。

¥In general, preloadedState wins over the state specified by the reducer. This lets reducers specify initial data that makes sense to them as default arguments, but also allows loading existing data (fully or partially) when you're hydrating the store from some persistent storage or the server.

注意:当传递 undefinedstate 时,初始状态使用 preloadedState 填充的 reducer 仍需要提供默认值来处理。所有 reducer 在初始化时都会传递 undefined,因此应将它们编写为当给定 undefined 时,应返回一些值。这可以是任何非 undefined 值;无需将 preloadedState 部分复制为默认值。

¥Note: Reducers whose initial state is populated using preloadedState will still need to provide a default value to handle when passed a state of undefined. All reducers are passed undefined on initialization, so they should be written such that when given undefined, some value should be returned. This can be any non-undefined value; there's no need to duplicate the section of preloadedState here as the default.

深入

¥In Depth

单简单 Reducer

¥Single Simple Reducer

首先让我们考虑只有一个 reducer 的情况。假设你不使用 combineReducers()

¥First let's consider a case where you have a single reducer. Say you don't use combineReducers().

那么你的 reducer 可能看起来像这样:

¥Then your reducer might look like this:

function counter(state = 0, action) {
switch (action.type) {
case 'INCREMENT':
return state + 1
case 'DECREMENT':
return state - 1
default:
return state
}
}

现在假设你用它创建了一个存储。

¥Now let's say you create a store with it.

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

初始状态为零。为什么?因为 createStore 的第二个参数是 undefined。这是 state 第一次传递到你的 reducer。当 Redux 初始化时,它会调度 "dummy" 操作来填充状态。所以你的 counter reducer 被称为 state 等于 undefined。这正是 "activates" 默认参数的情况。因此,根据默认 state 值 (state = 0),state 现在是 0。将返回该状态(0)。

¥The initial state is zero. Why? Because the second argument to createStore was undefined. This is the state passed to your reducer the first time. When Redux initializes it dispatches a "dummy" action to fill the state. So your counter reducer was called with state equal to undefined. This is exactly the case that "activates" the default argument. Therefore, state is now 0 as per the default state value (state = 0). This state (0) will be returned.

让我们考虑一个不同的场景:

¥Let's consider a different scenario:

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

为什么这次是 42,而不是 0?因为 createStore 是用 42 作为第二个参数调用的。该参数成为与虚拟操作一起传递给你的 reducer 的 state。这次,state 不是未定义的(它是 42!),因此默认参数语法不起作用。state4242 是 reducer 返回的。

¥Why is it 42, and not 0, this time? Because createStore was called with 42 as the second argument. This argument becomes the state passed to your reducer along with the dummy action. This time, state is not undefined (it's 42!), so default argument syntax has no effect. The state is 42, and 42 is returned from the reducer.

组合 Reducer

¥Combined Reducers

现在让我们考虑使用 combineReducers() 的情况。你有两个 reducer:

¥Now let's consider a case where you use combineReducers(). You have two reducers:

function a(state = 'lol', action) {
return state
}

function b(state = 'wat', action) {
return state
}

combineReducers({ a, b }) 生成的 reducer 看起来像这样:

¥The reducer generated by combineReducers({ a, b }) looks like this:

// const combined = combineReducers({ a, b })
function combined(state = {}, action) {
return {
a: a(state.a, action),
b: b(state.b, action)
}
}

如果我们在没有 preloadedState 的情况下调用 createStore,它将把 state 初始化为 {}。因此,当它调用 ab reducer 时,state.astate.b 将是 undefinedab reducer 都将接收 undefined 作为其 state 参数,如果它们指定默认 state 值,则将返回这些值。这就是组合 reducer 在第一次调用时返回 { a: 'lol', b: 'wat' } 状态对象的方式。

¥If we call createStore without the preloadedState, it's going to initialize the state to {}. Therefore, state.a and state.b will be undefined by the time it calls a and b reducers. Both a and b reducers will receive undefined as their state arguments, and if they specify default state values, those will be returned. This is how the combined reducer returns a { a: 'lol', b: 'wat' } state object on the first invocation.

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

让我们考虑一个不同的场景:

¥Let's consider a different scenario:

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' 默认参数相结合。

¥Now I specified the preloadedState as the argument to createStore(). The state returned from the combined reducer combines the initial state I specified for the a reducer with the 'wat' default argument specified that b reducer chose itself.

让我们回忆一下组合 reducer 的作用:

¥Let's recall what the combined reducer does:

// 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' } 作为返回的方式。

¥In this case, state was specified so it didn't fall back to {}. It was an object with a field equal to 'horse', but without the b field. This is why the a reducer received 'horse' as its state and gladly returned it, but the b reducer received undefined as its state and thus returned its idea of the default state (in our example, 'wat'). This is how we get { a: 'horse', b: 'wat' } in return.

回顾

¥Recap

总而言之,如果你坚持 Redux 约定并在使用 undefined 作为 state 参数调用 reducer 时返回初始状态(实现此目的的最简单方法是指定 state 默认参数值),那么你将 为组合 reducer 提供良好有用的行为。他们会更喜欢你传递给 createStore() 函数的 preloadedState 对象中的相应值,但如果你没有传递任何值,或者未设置相应的字段,则会选择 reducer 指定的默认 state 参数。这种方法效果很好,因为它提供了现有数据的初始化和水合作用,但如果数据未保留,则允许各个 reducer 重置其状态。当然,你可以递归地应用此模式,因为你可以在许多级别上使用 combineReducers(),甚至可以通过调用 reducer 并为它们提供状态树的相关部分来手动组成 reducer。

¥To sum this up, if you stick to Redux conventions and return the initial state from reducers when they're called with undefined as the state argument (the easiest way to implement this is to specify the state default argument value), you're going to have a nice useful behavior for combined reducers. They will prefer the corresponding value in the preloadedState object you pass to the createStore() function, but if you didn't pass any, or if the corresponding field is not set, the default state argument specified by the reducer is chosen instead. This approach works well because it provides both initialization and hydration of existing data, but lets individual reducers reset their state if their data was not preserved. Of course you can apply this pattern recursively, as you can use combineReducers() on many levels, or even compose reducers manually by calling reducers and giving them the relevant part of the state tree.