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 ( <> -

Prague itinerary

+

布拉格的行程安排

@@ -557,16 +561,16 @@ function tasksReducer(tasks, action) { return tasks.filter(t => t.id !== action.id); } default: { - throw Error('Unknown action: ' + action.type); + throw Error('未知 action: ' + action.type); } } } 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 } ]; ``` @@ -578,14 +582,14 @@ export default function AddTask({ onAddTask }) { return ( <> setText(e.target.value)} /> + }}>添加 ) } @@ -629,7 +633,7 @@ function Task({ task, onChange, onDelete }) { }); }} /> ); @@ -638,7 +642,7 @@ function Task({ task, onChange, onDelete }) { <> {task.text} ); @@ -657,7 +661,7 @@ function Task({ task, onChange, onDelete }) { /> {taskContent} ); @@ -672,7 +676,7 @@ ul, li { margin: 0; padding: 0; }
-If you want, you can even move the reducer to a different file: +如果有需要,你甚至可以把 reducer 移到一个单独的文件中: @@ -711,8 +715,8 @@ export default function TaskBoard() { } return ( - <> -

Prague itinerary

+ <> +

布拉格的行程安排

@@ -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 =>