使用 combineReducers
¥Using combineReducers
核心概念
¥Core Concepts
Redux 应用最常见的状态形状是一个普通的 Javascript 对象,其中每个顶层键包含 "切片" 个特定于域的数据。类似地,为该状态形状编写 reducer 逻辑的最常见方法是使用 "切片 reducer" 函数,每个函数都具有相同的 (state, action)
签名,并且每个函数负责管理对该特定状态片的所有更新。多个切片 reducer 可以响应同一个动作,根据需要独立更新自己的切片,并将更新后的切片组合成新的状态对象。
¥The most common state shape for a Redux app is a plain Javascript object containing "slices" of domain-specific data at each top-level key. Similarly, the most common approach to writing reducer logic for that state shape is to have "slice reducer" functions, each with the same (state, action)
signature, and each responsible for managing all updates to that specific slice of state. Multiple slice reducers can respond to the same action, independently update their own slice as needed, and the updated slices are combined into the new state object.
由于这种模式非常常见,Redux 提供了 combineReducers
实用程序来实现该行为。这是一个高阶 reducer 的示例,它采用一个充满切片 reducer 函数的对象,并返回一个新的 reducer 函数。
¥Because this pattern is so common, Redux provides the combineReducers
utility to implement that behavior. It is an example of a higher-order reducer, which takes an object full of slice reducer functions, and returns a new reducer function.
使用 combineReducers
时需要注意几个重要的想法:
¥There are several important ideas to be aware of when using combineReducers
:
首先,
combineReducers
只是一个实用函数,用于简化编写 Redux reducer 时最常见的用例。你不需要在自己的应用中使用它,并且它不能处理所有可能的情况。完全可以在不使用它的情况下编写 reducer 逻辑,并且对于combineReducer
不处理的情况需要编写自定义 reducer 逻辑是很常见的。(示例和建议参见 超越combineReducers
。)¥First and foremost,
combineReducers
is simply a utility function to simplify the most common use case when writing Redux reducers. You are not required to use it in your own application, and it does not handle every possible scenario. It is entirely possible to write reducer logic without using it, and it is quite common to need to write custom reducer logic for cases thatcombineReducer
does not handle. (See BeyondcombineReducers
for examples and suggestions.)虽然 Redux 本身对状态的组织方式并不固执,但
combineReducers
强制执行了几条规则来帮助用户避免常见错误。(详见combineReducers
。)¥While Redux itself is not opinionated about how your state is organized,
combineReducers
enforces several rules to help users avoid common errors. (SeecombineReducers
for details.)一个经常被问到的问题是 Redux "调用所有 reducer" 是否在调度一个动作时。由于实际上只有一个根 reducer 函数,因此默认答案是 "不,不是的"。然而,
combineReducers
具有确实以这种方式工作的特定行为。为了组装新的状态树,combineReducers
将使用其当前状态切片和当前操作来调用每个切片缩减器,从而使切片缩减器有机会响应并在需要时更新其状态切片。因此,从这个意义上说,使用combineReducers
可以做 "调用所有 reducer",或者至少可以做它所封装的所有切片 reducer。¥One frequently asked question is whether Redux "calls all reducers" when dispatching an action. Since there really is only one root reducer function, the default answer is "no, it does not". However,
combineReducers
has specific behavior that does work that way. In order to assemble the new state tree,combineReducers
will call each slice reducer with its current slice of state and the current action, giving the slice reducer a chance to respond and update its slice of state if needed. So, in that sense, usingcombineReducers
does "call all reducers", or at least all of the slice reducers it is wrapping.你可以在 reducer 结构的所有级别使用它,而不仅仅是创建根 reducer。在不同的地方有多个组合的 reducer 是很常见的,它们组合在一起创建根 reducer。
¥You can use it at all levels of your reducer structure, not just to create the root reducer. It's very common to have multiple combined reducers in various places, which are composed together to create the root reducer.
定义状态形状
¥Defining State Shape
有两种方法可以定义存储状态的初始形状和内容。首先,createStore
函数可以将 preloadedState
作为其第二个参数。这主要是为了使用之前保存在其他地方(例如浏览器的 localStorage)的状态来初始化存储。另一种方法是当状态参数为 undefined
时,根 reducer 返回初始状态值。初始化状态 中更详细地描述了这两种方法,但是使用 combineReducers
时还需要注意一些其他问题。
¥There are two ways to define the initial shape and contents of your store's state. First, the createStore
function can take preloadedState
as its second argument. This is primarily intended for initializing the store with state that was previously persisted elsewhere, such as the browser's localStorage. The other way is for the root reducer to return the initial state value when the state argument is undefined
. These two approaches are described in more detail in Initializing State, but there are some additional concerns to be aware of when using combineReducers
.
combineReducers
采用一个充满切片 reducer 函数的对象,并创建一个函数,该函数输出具有相同键的相应状态对象。这意味着,如果没有向 createStore
提供预加载状态,则输入切片缩减器对象中键的命名将定义输出状态对象中键的命名。这些名称之间的相关性并不总是显而易见,特别是在使用默认模块导出和对象字面量速记等功能时。
¥combineReducers
takes an object full of slice reducer functions, and creates a function that outputs a corresponding state object with the same keys. This means that if no preloaded state is provided to createStore
, the naming of the keys in the input slice reducer object will define the naming of the keys in the output state object. The correlation between these names is not always apparent, especially when using features such as default module exports and object literal shorthands.
下面是一个示例,说明如何使用 combineReducers
的对象字面量速记来定义状态形状:
¥Here's an example of how use of object literal shorthand with combineReducers
can define the state shape:
// reducers.js
export default theDefaultReducer = (state = 0, action) => state
export const firstNamedReducer = (state = 1, action) => state
export const secondNamedReducer = (state = 2, action) => state
// rootReducer.js
import { combineReducers, createStore } from 'redux'
import theDefaultReducer, {
firstNamedReducer,
secondNamedReducer
} from './reducers'
// Use object literal shorthand syntax to define the object shape
const rootReducer = combineReducers({
theDefaultReducer,
firstNamedReducer,
secondNamedReducer
})
const store = createStore(rootReducer)
console.log(store.getState())
// {theDefaultReducer : 0, firstNamedReducer : 1, secondNamedReducer : 2}
请注意,因为我们使用简写来定义对象字面量,所以结果状态中的键名称与导入中的变量名称相同。这可能并不总是理想的行为,并且对于那些不熟悉现代 JS 语法的人来说通常会造成混乱。
¥Notice that because we used the shorthand for defining an object literal, the key names in the resulting state are the same as the variable names from the imports. This may not always be the desired behavior, and is often a cause of confusion for those who aren't as familiar with modern JS syntax.
另外,由此产生的名称有点奇怪。在状态键名称中实际包含 "reducer" 这样的单词通常不是一个好习惯 - 密钥应该简单地反映它们所保存的数据的域或类型。这意味着我们应该显式指定切片缩减器对象中的键名称以定义输出状态对象中的键,或者在使用简写对象字面量语法时仔细重命名导入的切片缩减器的变量以设置键。
¥Also, the resulting names are a bit odd. It's generally not a good practice to actually include words like "reducer" in your state key names - the keys should simply reflect the domain or type of data they hold. This means we should either explicitly specify the names of the keys in the slice reducer object to define the keys in the output state object, or carefully rename the variables for the imported slice reducers to set up the keys when using the shorthand object literal syntax.
更好的用法可能如下所示:
¥A better usage might look like:
import { combineReducers, createStore } from 'redux'
// Rename the default import to whatever name we want. We can also rename a named import.
import defaultState, {
firstNamedReducer,
secondNamedReducer as secondState
} from './reducers'
const rootReducer = combineReducers({
defaultState, // key name same as the carefully renamed default export
firstState: firstNamedReducer, // specific key name instead of the variable name
secondState // key name same as the carefully renamed named export
})
const reducerInitializedStore = createStore(rootReducer)
console.log(reducerInitializedStore.getState())
// {defaultState : 0, firstState : 1, secondState : 2}
这种状态形状更好地反映了所涉及的数据,因为我们小心地设置了传递给 combineReducers
的密钥。
¥This state shape better reflects the data involved, because we took care to set up the keys we passed to combineReducers
.