diff --git a/beta/src/pages/learn/extracting-state-logic-into-a-reducer.md b/beta/src/pages/learn/extracting-state-logic-into-a-reducer.md
index 3d0ee12fad..5859477d4e 100644
--- a/beta/src/pages/learn/extracting-state-logic-into-a-reducer.md
+++ b/beta/src/pages/learn/extracting-state-logic-into-a-reducer.md
@@ -1,25 +1,29 @@
---
-title: Extracting State Logic into a Reducer
+title: 迁移状态逻辑至 Reducer 中
+translators:
+ - qinhua
+ - yyyang1996
+ - QC-L
---
-Components with many state updates spread across many event handlers can get overwhelming. For these cases, you can consolidate all the state update logic outside your component in a single function, called a "reducer."
+对于拥有许多状态更新逻辑的组件来说,过于分散的事件处理程序可能会令人不知所措。对于这种情况,你可以将组件的所有状态更新逻辑整合到一个外部函数中,这个函数叫作 “reducer”。
-- What a reducer function is
-- How to refactor `useState` to `useReducer`
-- When to use a reducer
-- How to write one well
+- 什么是 reducer 函数
+- 如何将 `useState` 重构成 `useReducer`
+- 什么时候使用 reducer
+- 如何编写一个好的 reducer
-## Consolidate state logic with a reducer {/*consolidate-state-logic-with-a-reducer*/}
+## 使用 reducer 整合状态逻辑 {/*consolidate-state-logic-with-a-reducer*/}
-As your components grow in complexity, it can get harder to see all the different ways that a component's state gets updated at a glance. For example, the `TaskBoard` component below holds an array of `tasks` in state and uses three different event handlers to add, remove, and edit tasks:
+随着组件复杂度的增加,你将很难一眼看清所有的组件状态更新逻辑。例如,下面的 `TaskBoard` 组件有一个数组类型的状态 `tasks`,并通过三个不同的事件处理程序来实现任务的添加、删除和修改:
@@ -57,7 +61,7 @@ export default function TaskBoard() {
return (
<>
-
Prague itinerary
+
布拉格的行程安排
@@ -72,9 +76,9 @@ export default function TaskBoard() {
let nextId = 3;
const initialTasks = [
- { id: 0, text: 'Visit Kafka Museum', done: true },
- { id: 1, text: 'Watch a puppet show', done: false },
- { id: 2, text: 'Lennon Wall pic', done: false },
+ { id: 0, text: '参观卡夫卡博物馆', done: true },
+ { id: 1, text: '看木偶戏', done: false },
+ { id: 2, text: '打卡列侬墙', done: false },
];
```
@@ -86,14 +90,14 @@ export default function AddTask({ onAddTask }) {
return (
<>
setText(e.target.value)}
/>
+ }}>添加
>
)
}
@@ -137,7 +141,7 @@ function Task({ task, onChange, onDelete }) {
});
}} />
>
);
@@ -146,7 +150,7 @@ function Task({ task, onChange, onDelete }) {
<>
{task.text}
>
);
@@ -165,7 +169,7 @@ function Task({ task, onChange, onDelete }) {
/>
{taskContent}
);
@@ -180,17 +184,17 @@ ul, li { margin: 0; padding: 0; }
-Each of its event handlers calls `setTasks` in order to update the state. As this component grows, so does the amount of state logic sprinkled throughout it. To reduce this complexity and keep all your logic in one easy-to-access place, you can move that state logic into a single function outside your component, **called a "reducer."**
+这个组件的每个事件处理程序都通过 `setTasks` 来更新状态。随着这个组件的不断迭代,其状态逻辑也会越来越多。为了降低这种复杂度,并让所有逻辑都可以存放在一个易于理解的地方,你可以将这些状态逻辑移到组件之外的一个称为 **reducer** 的函数中。
-Reducers are a different way to handle state. You can migrate from `useState` to `useReducer` in three steps:
+Reducer 是处理状态的另一种方式。你可以通过三个步骤将 `useState` 迁移到 `useReducer`:
-1. **Move** from setting state to dispatching actions.
-2. **Write** a reducer function.
-3. **Use** the reducer from your component.
+1. 将设置状态的逻辑 **修改** 成 dispatch 一个 action;
+2. **编写** 一个 reducer 函数;
+3. 在你的组件中 **使用** reducer。
-### Step 1: Move from setting state to dispatching actions {/*step-1-move-from-setting-state-to-dispatching-actions*/}
+### 第 1 步: 将设置状态的逻辑修改成 dispatch 一个 action {/*step-1-move-from-setting-state-to-dispatching-actions*/}
-Your event handlers currently specify *what to do* by setting state:
+你的事件处理程序目前是通过设置状态来实现逻辑的:
```js
function handleAddTask(text) {
@@ -218,13 +222,13 @@ function handleDeleteTask(taskId) {
}
```
-Remove all the state setting logic. What you are left with are three event handlers:
+移除所有的状态设置逻辑。只留下三个事件处理函数:
-* `handleAddTask(text)` is called when the user presses "Add".
-* `handleChangeTask(task)` is called when the user toggles a task or presses "Save".
-* `handleDeleteTask(taskId)` is called when the user presses "Delete".
+* `handleAddTask(text)` 在用户点击 “添加” 时被调用。
+* `handleChangeTask(task)` 在用户切换任务或点击 “保存” 时被调用。
+* `handleDeleteTask(taskId)` 在用户点击 “删除” 时被调用。
-Managing state with reducers is slightly different from directly setting state. Instead of telling React "what to do" by setting state, you specify "what the user just did" by dispatching "actions" from your event handlers. (The state update logic will live elsewhere!) So instead of "setting `tasks`" via event handler, you're dispatching an "added/removed/deleted a task" action. This is more descriptive of the user's intent.
+使用 reducers 管理状态与直接设置状态略有不同。它不是通过设置状态来告诉 React “要做什么”,而是通过事件处理程序 dispatch 一个 “action” 来指明 “用户刚刚做了什么”。(而状态更新逻辑则保存在其他地方!)因此,我们不再通过事件处理器直接“设置任务”,而是 dispatch 一个 “添加/修改/删除任务” 的 action。这更加符合用户的思维。
```js
function handleAddTask(text) {
@@ -250,12 +254,12 @@ function handleDeleteTask(taskId) {
}
```
-The object you pass to `dispatch` is called an "action:"
+你传递给 `dispatch` 的对象叫做 "action":
```js {3-7}
function handleDeleteTask(taskId) {
dispatch(
- // "action" object:
+ // "action" 对象:
{
type: 'deleted',
id: taskId
@@ -264,41 +268,41 @@ function handleDeleteTask(taskId) {
}
```
-It is a regular JavaScript object. You decide what to put in it, but generally it should contain the minimal information about *what happened*. (You will add the `dispatch` function itself in a later step.)
+它是一个普通的 JavaScript 对象。它的结构是由你决定的,但通常来说,它应该至少包含可以表明 *发生了什么事情* 的信息。(在后面的步骤中,你将会学习如何添加一个 `dispatch` 函数。)
-An action object can have any shape. By convention, it is common to give it a string `type` that describes what happened, and pass any additional information in other fields. The `type` is specific to a component, so in this example either `'added'` or `'added_task'` would be fine. Choose a name that says what happened!
+action 对象可以有多种结构。按照惯例,我们通常会添加一个字符串类型的 `type` 字段来描述发生了什么,并通过其它字段传递额外的信息。`type` 是特定于组件的,在这个例子中 `added` 和 `addded_task` 都可以。选一个能描述清楚发生的事件的名字!
```js
dispatch({
- // specific to component
+ // 针对特定的组件
type: 'what_happened',
- // other fields go here
+ // 其它字段放这里
});
```
-### Step 2: Write a reducer function {/*step-2-write-a-reducer-function*/}
+### 第 2 步: 编写一个 reducer 函数 {/*step-2-write-a-reducer-function*/}
-A reducer function is where you will put your state logic. It takes two arguments, the current state and the action object, and it returns the next state:
+reducer 函数就是你放置状态逻辑的地方。它接受两个参数,分别为当前 state 和 action 对象,并且返回的是更新后的 state:
```js
function yourReducer(state, action) {
- // return next state for React to set
+ // 给 React 返回更新后的状态
}
```
-React will set the state to what you return from the reducer.
+React 会将状态设置为你从 reducer 返回的状态。
-To move your state setting logic from your event handlers to a reducer function in this example, you will:
+在这个例子中,要将状态设置逻辑从事件处理程序移到 reducer 函数中,你需要:
-1. Declare the current state (`tasks`) as the first argument.
-2. Declare the `action` object as the second argument.
-3. Return the *next* state from the reducer (which React will set the state to).
+1. 声明当前状态(`tasks`)作为第一个参数;
+2. 声明 `action` 对象作为第二个参数;
+3. 从 `reducer` 返回 *下一个* 状态(React 会将旧的状态设置为这个最新的状态)。
-Here is all the state setting logic migrated to a reducer function:
+下面是所有迁移到 `reducer` 函数的状态设置逻辑:
```js
function tasksReducer(tasks, action) {
@@ -319,16 +323,16 @@ function tasksReducer(tasks, action) {
} else if (action.type === 'deleted') {
return tasks.filter(t => t.id !== action.id);
} else {
- throw Error('Unknown action: ' + action.type);
+ throw Error('未知 action: ' + action.type);
}
}
```
-> Because the reducer function takes state (`tasks`) as an argument, you can **declare it outside of your component.** This decreases the indentation level and can make your code easier to read.
+> 由于 `reducer` 函数接受 `state`(tasks)作为参数,因此你可以 **在组件之外声明它**。**这减少了代码的缩进级别,提升了代码的可读性。
-The code above uses if/else statements, but it's a convention to use [switch statements](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/switch) inside reducers. The result is the same, but it can be easier to read switch statements at a glance. We'll be using them throughout the rest of this documentation like so:
+上面的代码使用了 `if/else` 语句,但是在 reducers 中使用 [switch 语句](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/switch) 是一种惯例。两种方式结果是相同的,但 `switch` 语句读起来一目了然。在本文档的后面部分我们会像这样使用:
```js
function tasksReducer(tasks, action) {
@@ -353,24 +357,24 @@ function tasksReducer(tasks, action) {
return tasks.filter(t => t.id !== action.id);
}
default: {
- throw Error('Unknown action: ' + action.type);
+ throw Error('未知 action: ' + action.type);
}
}
}
```
-We recommend to wrap each `case` block into the `{` and `}` curly braces so that variables declared inside of different `case`s don't clash with each other. Also, a `case` should usually end with a `return`. If you forget to `return`, the code will "fall through" to the next `case`, which can lead to mistakes!
+我们建议将每个 `case` 块包装到 `{` 和 `}` 花括号中,这样在不同 `case` 中声明的变量就不会互相冲突。此外,`case` 通常应该以 `return` 结尾。如果你忘了 `return`,代码就会 `进入` 到下一个 `case`,这就会导致错误!
-If you're not yet comfortable with switch statements, using if/else is completely fine.
+如果你还不熟悉 `switch` 语句,使用 `if/else` 也是可以的。
-
+
-Although reducers can "reduce" the amount of code inside your component, they are actually named after the [`reduce()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce) operation that you can perform on arrays.
+尽管 `reducer` 可以 “减少” 组件内的代码量,但它实际上是以数组上的 [`reduce()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce) 方法命名的。
-The `reduce()` operation lets you take an array and "accumulate" a single value out of many:
+`reduce()` 允许你将数组中的多个值 “累加” 成一个值:
```
const arr = [1, 2, 3, 4, 5];
@@ -379,9 +383,9 @@ const sum = arr.reduce(
); // 1 + 2 + 3 + 4 + 5
```
-The function you pass to `reduce` is known as a "reducer". It takes the _result so far_ and the _current item,_ then it returns the _next result._ React reducers are an example of the same idea: they take the _state so far_ and the _action_, and return the _next state._ In this way, they accumulate actions over time into state.
+你传递给 `reduce` 的函数被称为 “reducer”。它接受 `目前的结果` 和 `当前的值`,然后返回 `下一个结果`。React 中的 `reducer` 和这个是一样的:它们都接受 `目前的状态` 和 `action` ,然后返回 `下一个状态`。这样,action 会随着时间推移累积到状态中。
-You could even use the `reduce()` method with an `initialState` and an array of `actions` to calculate the final state by passing your reducer function to it:
+你甚至可以使用 `reduce()` 方法以及 `initialState` 和 `actions` 数组,通过传递你的 `reducer` 函数来计算最终的状态:
@@ -390,10 +394,10 @@ import tasksReducer from './tasksReducer.js';
let initialState = [];
let actions = [
- { type: 'added', id: 1, text: 'Visit Kafka Museum' },
- { type: 'added', id: 2, text: 'Watch a puppet show' },
+ { type: 'added', id: 1, text: '参观卡夫卡博物馆' },
+ { type: 'added', id: 2, text: '看木偶戏' },
{ type: 'deleted', id: 1 },
- { type: 'added', id: 3, text: 'Lennon Wall pic' },
+ { type: 'added', id: 3, text: '打卡列侬墙' },
];
let finalState = actions.reduce(
@@ -435,7 +439,7 @@ export default function tasksReducer(
return tasks.filter(t => t.id !== action.id);
}
default: {
- throw Error('Unknown action: ' + action.type);
+ throw Error('未知 action: ' + action.type);
}
}
}
@@ -447,13 +451,13 @@ export default function tasksReducer(
-You probably won't need to do this yourself, but this is similar to what React does!
+你可能不需要自己做这些,但这与 React 所做的很相似!
-### Step 3: Use the reducer from your component {/*step-3-use-the-reducer-from-your-component*/}
+### 第 3 步: 在组件中使用 reducer {/*step-3-use-the-reducer-from-your-component*/}
-Finally, you need to hook up the `tasksReducer` to your component. Make sure to import the `useReducer` Hook from React:
+最后,你需要将 `tasksReducer` 导入到组件中。记得先从 React 中导入 `useReducer` Hook:
```js
import { useReducer } from 'react';
@@ -471,19 +475,19 @@ with `useReducer` like so:
const [tasks, dispatch] = useReducer(tasksReducer, initialTasks);
```
-The `useReducer` Hook is similar to `useState`—you must pass it an initial state and it returns a stateful value and a way to set state (in this case, the dispatch function). But it's a little different.
+`useReducer` 和 `useState` 很相似——你必须给它传递一个初始状态,它会返回一个有状态的值和一个设置该状态的函数(在这个例子中就是 dispatch 函数)。但是,它们两个之间还是有点差异的。
-The `useReducer` Hook takes two arguments:
+`useReducer` 钩子接受 2 个参数:
-1. A reducer function
-2. An initial state
+1. 一个 reducer 函数
+2. 一个初始的 state
-And it returns:
+它返回如下内容:
-1. A stateful value
-2. A dispatch function (to "dispatch" user actions to the reducer)
+1. 一个有状态的值
+2. 一个 dispatch 函数(用来 “派发” 用户操作给 reducer)
-Now it's fully wired up! Here, the reducer is declared at the bottom of the component file:
+现在一切都准备就绪了!我们在这里把 reducer 定义在了组件的末尾:
@@ -522,7 +526,7 @@ export default function TaskBoard() {
return (
<>
-
@@ -727,9 +731,9 @@ export default function TaskBoard() {
let nextId = 3;
const initialTasks = [
- { id: 0, text: 'Visit Kafka Museum', done: true },
- { id: 1, text: 'Watch a puppet show', done: false },
- { id: 2, text: 'Lennon Wall pic', done: false },
+ { id: 0, text: '参观卡夫卡博物馆', done: true },
+ { id: 1, text: '看木偶戏', done: false },
+ { id: 2, text: '打卡列侬墙', done: false },
];
```
@@ -759,7 +763,7 @@ export default function tasksReducer(
return tasks.filter(t => t.id !== action.id);
}
default: {
- throw Error('Unknown action: ' + action.type);
+ throw Error('未知 action:' + action.type);
}
}
}
@@ -773,14 +777,14 @@ export default function AddTask({ onAddTask }) {
return (
<>
setText(e.target.value)}
/>
+ }}>添加
>
)
}
@@ -824,7 +828,7 @@ function Task({ task, onChange, onDelete }) {
});
}} />
>
);
@@ -833,7 +837,7 @@ function Task({ task, onChange, onDelete }) {
<>
{task.text}
>
);
@@ -852,7 +856,7 @@ function Task({ task, onChange, onDelete }) {
/>
{taskContent}
);
@@ -867,30 +871,30 @@ ul, li { margin: 0; padding: 0; }
-Component logic can be easier to read when you separate concerns like this. Now the event handlers only specify *what happened* by dispatching actions, and the reducer function determines *how the state updates* in response to them.
+当像这样分离关注点时,我们可以更容易地理解组件逻辑。现在,事件处理程序只通过派发 `action` 来指定 *发生了什么*,而 `reducer` 函数通过响应 `actions` 来决定 *状态如何更新*。
-## Comparing `useState` and `useReducer` {/*comparing-usestate-and-usereducer*/}
+## `useState` 和 `useReducer` 的对比 {/*comparing-usestate-and-usereducer*/}
-Reducers are not without downsides! Here's a few ways you can compare them:
+Reducers 并非没有缺点!以下是比较它们的几种方法:
-* **Code size:** Generally, with `useState` you have to write less code upfront. With `useReducer`, you have to write both a reducer function _and_ dispatch actions. However, `useReducer` can help cut down on the code if many event handlers modify state in a similar way.
-* **Readability:** `useState` is very easy to read when the state updates are simple. When they get more complex, they can bloat your component's code and make it difficult to scan. In this case, `useReducer` lets you cleanly separate the *how* of update logic from the *what happened* of event handlers.
-* **Debugging:** When you have a bug with `useState`, it can be difficult to tell _where_ the state was set incorrectly, and _why_. With `useReducer`, you can add a console log into your reducer to see every state update, and _why_ it happened (due to which `action`). If each `action` is correct, you'll know that the mistake is in the reducer logic itself. However, you have to step through more code than with `useState`.
-* **Testing:** A reducer is a pure function that doesn't depend on your component. This means that you can export and test it separately in isolation. While generally it's best to test components in a more realistic environment, for complex state update logic it can be useful to assert that your reducer returns a particular state for a particular initial state and action.
-* **Personal preference:** Some people like reducers, others don't. That's okay. It's a matter of preference. You can always convert between `useState` and `useReducer` back and forth: they are equivalent!
+* **代码体积:** 通常,在使用 `useState` 时,一开始只需要编写少量代码。而 `useReducer` 必须提前编写 reducer 函数和需要调度的 actions。但是,当多个事件处理程序以相似的方式修改 state 时,`useReducer` 可以减少代码量。
+* **可读性:** 当状态更新逻辑足够简单时,`useState` 的可读性还行。但是,一旦逻辑变得复杂起来,它们会使组件变得臃肿且难以阅读。在这种情况下,`useReducer` 允许你将状态更新逻辑与事件处理程序分离开来。
+* **可调试性:** 当使用 `useState` 出现问题时, 你很难发现具体原因以及为什么。 而使用 `useReducer` 时, 你可以在 reducer 函数中通过打印日志的方式来观察每个状态的更新,以及为什么要更新(来自哪个 `action`)。 如果所有 `action` 都没问题,你就知道问题出在了 reducer 本身的逻辑中。 然而,与使用 `useState` 相比,你必须单步执行更多的代码。
+* **可测试性:** reducer 是一个不依赖于组件的纯函数。这就意味着你可以单独对它进行测试。一般来说,我们最好是在真实环境中测试组件,但对于复杂的状态更新逻辑,针对特定的初始状态和 `action`,断言 reducer 返回的特定状态会很有帮助。
+* **个人偏好:** 并不是所有人都喜欢用 reducer,没关系,这是个人偏好问题。你可以随时在 `useState` 和 `useReducer` 之间切换,它们能做的事情是一样的!
-We recommend using a reducer if you often encounter bugs due to incorrect state updates in some component, and want to introduce more structure to its code. You don't have to use reducers for everything: feel free to mix and match! You can even `useState` and `useReducer` in the same component.
+如果你在修改某些组件状态时经常出现问题或者想给组件添加更多逻辑时,我们建议你还是使用 reducer。当然,你也不必整个项目都用 reducer,这是可以自由搭配的。你甚至可以在一个组件中同时使用 `useState` 和 `useReducer`。
-## Writing reducers well {/*writing-reducers-well*/}
+## 编写一个好的 reducers {/*writing-reducers-well*/}
-Keep these two tips in mind when writing reducers:
+编写 `reducers` 时最好牢记以下两点:
-* **Reducers must be pure.** Similar to [state updater functions](/learn/queueing-a-series-of-state-updates), reducers run during rendering! (Actions are queued until the next render.) This means that reducers [must be pure](/learn/keeping-components-pure)—same inputs always result in the same output. They should not send requests, schedule timeouts, or perform any side effects (operations that impact things outside the component). They should update [objects](/learn/updating-objects-in-state) and [arrays](/learn/updating-arrays-in-state) without mutations.
-* **Actions describe "what happened," not "what to do."** For example, if a user presses "Reset" on a form with five fields managed by a reducer, it makes more sense to dispatch one `reset_form` action rather than five separate `set_field` actions. If you log every action in a reducer, that log should be clear enough for you to reconstruct what interactions or responses happened in what order. This helps with debugging!
+* **reducers 必须是纯净的。** 这一点和 [状态更新函数](/learn/queueing-a-series-of-state-updates) 是相似的,`reducers` 在是在渲染时运行的!(actions 会排队直到下一次渲染)。 这就意味着 `reducers` [必须纯净](/learn/keeping-components-pure),即当输入相同时,输出也是相同的。它们不应该包含异步请求、定时器或者任何副作用(对组件外部有影响的操作)。它们应该以不可变值的方式去更新 [对象](/learn/updating-objects-in-state) 和 [数组](/learn/updating-arrays-in-state)。
+* **actions 用来描述 “发生了什么” ,而不是 “做什么”。** 举个例子,如果用户在一个由 `reducer` 管理的表单(包含五个表单项)中点击了 `重置按钮`,那么 dispatch 一个 `reset_form` 的 action 比 dispatch 五个单独的 `set_field` 的 action 更加合理。如果你在一个 `reducer` 中打印了所有的 `action` 日志,那么这个日志应该是很清晰的,它能让你以某种步骤复现已发生的交互或响应。这对代码调试很有帮助!
-## Writing concise reducers with Immer {/*writing-concise-reducers-with-immer*/}
+## 使用 Immer 简化 reducers {/*writing-concise-reducers-with-immer*/}
-Just like with [updating objects](/learn/updating-objects-in-state#write-concise-update-logic-with-immer) and [arrays](/learn/updating-arrays-in-state#write-concise-update-logic-with-immer) in regular state, you can use the Immer library to make reducers more concise. Here, [`useImmerReducer`](https://github.com/immerjs/use-immer#useimmerreducer) lets you mutate the state with `push` or `arr[i] =` assignment:
+与在平常的 state 中 [修改对象](/learn/updating-objects-in-state#write-concise-update-logic-with-immer) 和 [数组](/learn/updating-arrays-in-state#write-concise-update-logic-with-immer) 一样,你可以使用 `Immer` 这个库来简化 `reducer`。在这里,[`useImmerReducer`](https://github.com/immerjs/use-immer#useimmerreducer) 让你可以通过 `push` 或 `arr[i] =` 来修改 state :
@@ -920,7 +924,7 @@ function tasksReducer(draft, action) {
return draft.filter(t => t.id !== action.id);
}
default: {
- throw Error('Unknown action: ' + action.type);
+ throw Error('未知 action:' + action.type);
}
}
}
@@ -955,7 +959,7 @@ export default function TaskBoard() {
return (
<>
-
Prague itinerary
+
布拉格的行程安排
@@ -970,9 +974,9 @@ export default function TaskBoard() {
let nextId = 3;
const initialTasks = [
- { id: 0, text: 'Visit Kafka Museum', done: true },
- { id: 1, text: 'Watch a puppet show', done: false },
- { id: 2, text: 'Lennon Wall pic', done: false },
+ {id: 0, text: '参观卡夫卡博物馆', done: true},
+ {id: 1, text: '看木偶戏', done: false},
+ {id: 2, text: '打卡列侬墙', done: false},
];
```
@@ -984,14 +988,14 @@ export default function AddTask({ onAddTask }) {
return (
<>
setText(e.target.value)}
/>
+ }}>添加
>
)
}
@@ -1035,7 +1039,7 @@ function Task({ task, onChange, onDelete }) {
});
}} />
>
);
@@ -1044,7 +1048,7 @@ function Task({ task, onChange, onDelete }) {
<>
{task.text}
>
);
@@ -1063,7 +1067,7 @@ function Task({ task, onChange, onDelete }) {
/>
{taskContent}
);
@@ -1096,36 +1100,34 @@ ul, li { margin: 0; padding: 0; }
-Reducers must be pure, so they shouldn't mutate state. But Immer provides you with a special `draft` object which is safe to mutate. Under the hood, Immer will create a copy of your state with the changes you made to the `draft`. This is why reducers managed by `useImmerReducer` can mutate their first argument and don't need to return state.
+Reducers 应该是纯净的,所以它们不应该去修改 state。而 Immer 为你提供了一种特殊的 `draft` 对象,你可以通过它安全的修改 state。在底层,Immer 会基于当前 state 创建一个副本。这就是为什么通过 `useImmerReducer` 来管理 reducers 时,可以修改第一个参数,且不需要返回一个新的 state 的原因。
-* To convert from `useState` to `useReducer`:
- 1. Dispatch actions from event handlers.
- 2. Write a reducer function that returns the next state for a given state and action.
- 3. Replace `useState` with `useReducer`.
-* Reducers require you to write a bit more code, but they help with debugging and testing.
-* Reducers must be pure.
-* Actions describe "what happened," not "what to do."
-* Use Immer if you want to write reducers in a mutating style.
+* 把 `useState` 转化为 `useReducer`:
+ 1. 通过事件处理函数 dispatch actions;
+ 2. 编写一个 reducer 函数,它接受传入的 state 和一个 action,并返回一个新的 state;
+ 3. 使用 `useReducer` 替换 `useState`;
+* Reducers 可能需要你写更多的代码,但是这有利于代码的调试和测试。
+* Reducers 必须是纯净的。
+* Actions 描述的是 “发生了什么” 而不是 “要做什么”。
+* 使用 Immer 来帮助你在 reducer 里直接修改状态。
-
-
-### Dispatch actions from event handlers {/*dispatch-actions-from-event-handlers*/}
+### 通过事件处理函数 dispatch actions {/*dispatch-actions-from-event-handlers*/}
-Currently, the event handlers in `ContactList.js` and `Chat.js` have `// TODO` comments. This is why typing into the input doesn't work, and clicking on the buttons doesn't change the selected recipient.
+目前,`ContactList.js` 和 `Chat.js` 中的事件处理程序包含 `// TODO` 注释。这就是为什么输入不起作用,点击按钮也不会改变收件人的原因。
-Replace these two `// TODO`s with the code to `dispatch` the corresponding actions. To see the expected shape and the type of the actions, check the reducer in `messengerReducer.js`. The reducer is already written so you won't need to change it. You only need to dispatch the actions in `ContactList.js` and `Chat.js`.
+将这两个 `// TODO` 替换为 `dispatch` 相应的 action。如果要查看 action 的结构和类型,请查看 `messerreducer.js` 中的 reducer。reducer 已经写好了,你不需要再修改它。你只需要在 `ContactList.js` 和 `Chat.js` 中 dispatch 相应的 action 即可。
-The `dispatch` function is already available in both of these components because it was passed as a prop. So you need to call `dispatch` with the corresponding action object.
+`dispatch` 函数在这两个组件中都是可用的,因为它已经以 prop 的形式传递进来了。因此你需要通过传入相应的 action 对象来调用 `dispatch` 函数。
-To check the action object shape, you can look at the reducer and see which `action` fields it expects to see. For example, the `changed_selection` case in the reducer looks like this:
+要检查 action 的对象结构,你可以查看 reducer,看看它需要哪些字段。例如,reducer 中的 `changed_selection` 是这样的:
```js
case 'changed_selection': {
@@ -1136,7 +1138,7 @@ case 'changed_selection': {
}
```
-This means that your action object should have a `type: 'changed_selection'`. You also see the `action.contactId` being used, so you need to include a `contactId` property into your action.
+这表示你的 `action` 对象应该有一个 `type: 'changed_selection'`。同时你也可以看到代码中用到了 `action.contactId`,所以你需要传入一个 `contactId` 属性到你的 action 对象中。
@@ -1187,7 +1189,7 @@ const contacts = [
```js messengerReducer.js
export const initialState = {
selectedId: 0,
- message: 'Hello'
+ message: '你好'
};
export function messengerReducer(
@@ -1209,7 +1211,7 @@ export function messengerReducer(
};
}
default: {
- throw Error('Unknown action: ' + action.type);
+ throw Error('未知 action:' + action.type);
}
}
}
@@ -1227,7 +1229,7 @@ export default function ContactList({
{contacts.map(contact =>
);
}
@@ -1794,9 +1796,9 @@ textarea {
-This works and clears the input when you hit "Send."
+这样当你点击 “发送” 按钮时就会清空输入框。
-However, *from the user's perspective*, sending a message is a different action than editing the field. To reflect that, you could instead create a *new* action called `sent_message`, and handle it separately in the reducer:
+然而,从用户的角度来看,发送消息与编辑字段是不同的操作。为了体现这一点,你可以创建一个名为 `sent_message` 的新 *action*,并在 reducer 中单独处理:
@@ -1845,7 +1847,7 @@ const contacts = [
```js messengerReducer.js active
export const initialState = {
selectedId: 0,
- message: 'Hello'
+ message: '你好'
};
export function messengerReducer(
@@ -1873,7 +1875,7 @@ export function messengerReducer(
};
}
default: {
- throw Error('Unknown action: ' + action.type);
+ throw Error('未知 action:' + action.type);
}
}
}
@@ -1922,7 +1924,7 @@ export default function Chat({
);
}
@@ -1964,44 +1966,44 @@ textarea {
-The resulting behavior is the same. But keep in mind that action types should ideally describe "what the user did" rather than "how you want the state to change". This makes it easier to later add more features.
+结果虽然是一样的。但请记住,action 的类型应该准确描述 “用户做了什么”,而不是 “你希望状态如何改变”。这使得以后添加更多特性变的容易。
-With either solution, it's important that you **don't** place the `alert` inside a reducer. The reducer should be a pure function--it should only calculate the next state. It should not "do" anything, including displaying messages to the user. That should happen in the event handler. (To help catch mistakes like this, React will call your reducers multiple times in Strict Mode. This is why, if you put an alert in a reducer, it fires twice.)
+不管是哪一种解决方案,最重要的是你 **不要** 把 `alert` 放置在 reducer 中。reducer 必须是一个纯函数——它应该只计算下一个状态。而不应该 “做” 其它事情,包括向用户显示消息。这应该在事件处理程序中处理。(为了便于捕获这样的错误,React 会在严格模式下多次调用你的 reducer。这就是为什么当你在 reducer 中加入一个 alert,它会触发两次的原因。)
-### Restore input values when switching between tabs {/*restore-input-values-when-switching-between-tabs*/}
+### 切换 tab 时重置输入框内容 {/*restore-input-values-when-switching-between-tabs*/}
-In this example, switching between different recipients always clears the text input:
+在这个示例中,切换收件人时总是会清空输入框。
```js
case 'changed_selection': {
return {
...state,
selectedId: action.contactId,
- message: '' // Clears the input
+ message: '' // 清空输入框
};
```
-This is because you don't want to share a single message draft between several recipients. But it would be better if your app "remembered" a draft for each contact separately, restoring them when you switch contacts.
+这是因为你不希望在多个收件人之间共享单个邮件草稿。但如果你的应用程序能单独 “记住” 每个联系人的草稿,并在你切换联系人时恢复,那就更好了。
-Your task is to change the way the state is structured so that you remember a separate message draft *per contact*. You would need to make a few changes to the reducer, the initial state, and the components.
+你的任务是改变状态的组织形式,以便能记住 *每个联系人* 的消息草稿。你需要对 reducer、初始状态和组件进行一些修改。
-You can structure your state like this:
+你可以像下面这样组织 state:
```js
export const initialState = {
selectedId: 0,
messages: {
- 0: 'Hello, Taylor', // Draft for contactId = 0
- 1: 'Hello, Alice' // Draft for contactId = 1
+ 0: 'Hello, Taylor', // contactId = 0 的草稿
+ 1: 'Hello, Alice' // contactId = 1 的草稿
}
};
```
-The `[key]: value` [computed property](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Object_initializer#computed_property_names) syntax can help you update the `messages` object:
+这种 `[key]: value` [计算属性](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Object_initializer#computed_property_names) 可以帮你更新 `messages` 对象:
```js
{
@@ -2059,7 +2061,7 @@ const contacts = [
```js messengerReducer.js
export const initialState = {
selectedId: 0,
- message: 'Hello'
+ message: '你好'
};
export function messengerReducer(
@@ -2087,7 +2089,7 @@ export function messengerReducer(
};
}
default: {
- throw Error('Unknown action: ' + action.type);
+ throw Error('未知 action:' + action.type);
}
}
}
@@ -2136,7 +2138,7 @@ export default function Chat({
);
}
@@ -2183,15 +2185,15 @@ textarea {
You'll need to update the reducer to store and update a separate message draft per contact:
```js
-// When the input is edited
+// 当输入框内容被修改时
case 'edited_message': {
return {
- // Keep other state like selection
+ // 保存其它的 state,比如当前选中的
...state,
messages: {
- // Keep messages for other contacts
+ // 保存其他联系人的消息
...state.messages,
- // But change the selected contact's message
+ // 改变当前联系人的消息
[state.selectedId]: action.message
}
};
@@ -2290,7 +2292,7 @@ export function messengerReducer(
};
}
default: {
- throw Error('Unknown action: ' + action.type);
+ throw Error('未知 action:' + action.type);
}
}
}
@@ -2339,7 +2341,7 @@ export default function Chat({
);
}
@@ -2381,19 +2383,19 @@ textarea {
-Notably, you didn't need to change any of the event handlers to implement this different behavior. Without a reducer, you would have to change every event handler that updates the state.
+显然,你不再需要通过修改任何事件处理程序来实现不同的行为。但如果没使用 reducer 的话,你不得不在每个事件处理程序中去更新状态。
-### Implement `useReducer` from scratch {/*implement-usereducer-from-scratch*/}
+### 从零开始实现 `useReducer` {/*implement-usereducer-from-scratch*/}
-In the earlier examples, you imported the `useReducer` Hook from React. This time, you will implement *the `useReducer` Hook itself!* Here is a stub to get your started. It shouldn't take more than 10 lines of code.
+在前面的例子中,你从 React 中导入了 `useReducer` Hook。现在,你将学习自己实现 `useReducer` Hook。你可以从这个模板开始,它不会超过 10 行代码。
-To test your changes, try typing into the input or select a contact.
+为了验证你的修改,试着在输入框中输入文字或选择联系人。
-Here is a more detailed sketch of the implementation:
+下面是一个更加详细的基本实现:
```js
export function useReducer(reducer, initialState) {
@@ -2407,7 +2409,7 @@ export function useReducer(reducer, initialState) {
}
```
-Recall that a reducer function takes two arguments--the current state and the action object--and it returns the next state. What should your `dispatch` implementation do with it?
+回想一下,reducer 函数接受两个参数——当前的 state 和 action 对象——并返回下一个 state。你的 `dispatch` 应该用它做什么?
@@ -2495,7 +2497,7 @@ export function messengerReducer(
};
}
default: {
- throw Error('Unknown action: ' + action.type);
+ throw Error('未知 action:' + action.type);
}
}
}
@@ -2556,7 +2558,7 @@ export default function Chat({
);
}
@@ -2600,7 +2602,7 @@ textarea {
-Dispatching an action calls a reducer with the current state and the action, and stores the result as the next state. This is what it looks like in code:
+dispatch 一个 action 去调用一个具有当前 state 和 action 的 reducer,并将结果存储为下一个 state。下面是它在代码中的样子:
@@ -2686,7 +2688,7 @@ export function messengerReducer(
};
}
default: {
- throw Error('Unknown action: ' + action.type);
+ throw Error('未知 action:' + action.type);
}
}
}
@@ -2750,7 +2752,7 @@ export default function Chat({
);
}
@@ -2792,7 +2794,7 @@ textarea {
-Though it doesn't matter in most cases, a slightly more accurate implementation looks like this:
+虽然在大多数情况下这并不重要,但更准确的实现是这样的:
```js
function dispatch(action) {
@@ -2800,7 +2802,7 @@ function dispatch(action) {
}
```
-This is because the dispatched actions are queued until the next render, [similar to the updater functions](/learn/queueing-a-series-of-state-updates).
+这是因为被派发的 actions 在下一次渲染之前都是处于排队状态的,这和 [状态更新函数](/learn/queueing-a-series-of-state-updates) 类似。