Skip to main content

reducer 基本结构和状态形状

¥Basic Reducer Structure and State Shape

Reducer 基本结构

¥Basic Reducer Structure

首先也是最重要的,重要的是要了解你的整个应用实际上只有一个 reducer 函数:你作为第一个参数传递给 createStore 的函数。单个 reducer 函数最终需要完成几件事:

¥First and foremost, it's important to understand that your entire application really only has one single reducer function: the function that you've passed into createStore as the first argument. That one single reducer function ultimately needs to do several things:

  • 第一次调用 reducer 时,state 的值将是 undefined。reducer 需要通过在处理传入操作之前提供默认状态值来处理这种情况。

    ¥The first time the reducer is called, the state value will be undefined. The reducer needs to handle this case by supplying a default state value before handling the incoming action.

  • 它需要查看之前的状态和调度的动作,并确定需要完成什么样的工作

    ¥It needs to look at the previous state and the dispatched action, and determine what kind of work needs to be done

  • 假设需要发生实际更改,它需要使用更新的数据创建新的对象和数组并返回它们

    ¥Assuming actual changes need to occur, it needs to create new objects and arrays with the updated data and return those

  • 如果不需要更改,它应该按原样返回现有状态。

    ¥If no changes are needed, it should return the existing state as-is.

编写 reducer 逻辑的最简单方法是将所有内容放入单个函数声明中,如下所示:

¥The simplest possible approach to writing reducer logic is to put everything into a single function declaration, like this:

function counter(state, action) {
if (typeof state === 'undefined') {
state = 0 // If state is undefined, initialize it with a default value
}

if (action.type === 'INCREMENT') {
return state + 1
} else if (action.type === 'DECREMENT') {
return state - 1
} else {
return state // In case an action is passed in we don't understand
}
}

请注意,这个简单的函数满足了所有基本要求。如果不存在则返回默认值,初始化存储;它根据操作的类型确定需要进行哪种更新,并返回新值;如果不需要做任何工作,它会返回之前的状态。

¥Notice that this simple function fulfills all the basic requirements. It returns a default value if none exists, initializing the store; it determines what sort of update needs to be done based on the type of the action, and returns new values; and it returns the previous state if no work needs to be done.

可以对此 reducer 进行一些简单的调整。首先,重复的 if/else 语句很快就会变得令人厌烦,因此使用 switch 语句代替是很常见的。其次,我们可以使用默认参数值来处理初始 "没有现有数据" 情况。通过这些更改,reducer 将如下所示:

¥There are some simple tweaks that can be made to this reducer. First, repeated if/else statements quickly grow tiresome, so it's very common to use switch statements instead. Second, we can use default parameter values to handle the initial "no existing data" case. With those changes, the reducer would look like:

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

这是典型 Redux reducer 函数使用的基本结构。

¥This is the basic structure that a typical Redux reducer function uses.

基本状态形状

¥Basic State Shape

Redux 鼓励你根据需要管理的数据来考虑你的应用。任何给定时间点的数据都是应用的 "state",该状态的结构和组织通常称为其 "shape"。状态的形状对于如何构建 reducer 逻辑起着重要作用。

¥Redux encourages you to think about your application in terms of the data you need to manage. The data at any given point in time is the "state" of your application, and the structure and organization of that state is typically referred to as its "shape". The shape of your state plays a major role in how you structure your reducer logic.

Redux 状态通常有一个普通的 Javascript 对象作为状态树的顶部。(当然可以使用另一种类型的数据来代替,例如单个数字、数组或专用数据结构,但大多数库都假设顶层值是普通对象。)在顶层对象中组织数据的最常见方法是将数据进一步划分为子树,其中每个顶层键代表一些 "domain" 或 "slice" 的相关数据。例如,基本的 Todo 应用的状态可能如下所示:

¥A Redux state usually has a plain Javascript object as the top of the state tree. (It is certainly possible to have another type of data instead, such as a single number, an array, or a specialized data structure, but most libraries assume that the top-level value is a plain object.) The most common way to organize data within that top-level object is to further divide data into sub-trees, where each top-level key represents some "domain" or "slice" of related data. For example, a basic Todo app's state might look like:

{
visibilityFilter: 'SHOW_ALL',
todos: [
{
text: 'Consider using Redux',
completed: true,
},
{
text: 'Keep all state in a single tree',
completed: false
}
]
}

在此示例中,todosvisibilityFilter 都是状态中的顶层键,每个都代表某个特定概念的 "slice" 数据。

¥In this example, todos and visibilityFilter are both top-level keys in the state, and each represents a "slice" of data for some particular concept.

大多数应用处理多种类型的数据,这些数据大致可分为三类:

¥Most applications deal with multiple types of data, which can be broadly divided into three categories:

  • 域数据:应用需要显示、使用或修改的数据(例如 "从服务器检索到的所有待办事项")

    ¥Domain data: data that the application needs to show, use, or modify (such as "all of the Todos retrieved from the server")

  • 应用状态:特定于应用行为的数据(例如 "当前选择了待办事项 #5" 或 "有一个获取 Todos 的请求正在进行中")

    ¥App state: data that is specific to the application's behavior (such as "Todo #5 is currently selected", or "there is a request in progress to fetch Todos")

  • 用户界面状态:表示 UI 当前显示方式的数据(例如 "EditTodo 模式对话框当前打开")

    ¥UI state: data that represents how the UI is currently displayed (such as "The EditTodo modal dialog is currently open")

由于存储代表应用的核心,因此你应该根据域数据和应用状态而不是 UI 组件树来定义状态形状。例如,state.leftPane.todoList.todos 的形状是一个坏主意,因为 "todos" 的想法是整个应用的核心,而不仅仅是 UI 的单个部分。todos 切片应该位于状态树的顶部。

¥Because the store represents the core of your application, you should define your state shape in terms of your domain data and app state, not your UI component tree. As an example, a shape of state.leftPane.todoList.todos would be a bad idea, because the idea of "todos" is central to the whole application, not just a single part of the UI. The todos slice should be at the top of the state tree instead.

UI 树和状态形状之间很少会存在一一对应的关系。例外情况可能是,如果你也在 Redux 存储中显式跟踪 UI 数据的各个方面,但即使如此,UI 数据的形状和域数据的形状也可能不同。

¥There will rarely be a 1-to-1 correspondence between your UI tree and your state shape. The exception to that might be if you are explicitly tracking various aspects of UI data in your Redux store as well, but even then the shape of the UI data and the shape of the domain data would likely be different.

典型应用的状态形状可能大致如下所示:

¥A typical app's state shape might look roughly like:

{
domainData1 : {},
domainData2 : {},
appState1 : {},
appState2 : {},
ui : {
uiState1 : {},
uiState2 : {},
}
}