From 875a7bc571c81e183a0c2645ecbf5c78beba2635 Mon Sep 17 00:00:00 2001 From: davont Date: Thu, 23 Mar 2023 20:55:03 +0800 Subject: [PATCH 01/30] docs(cn): translate the choosing-the-state-structure page --- .../learn/choosing-the-state-structure.md | 230 +++++++++--------- 1 file changed, 120 insertions(+), 110 deletions(-) diff --git a/src/content/learn/choosing-the-state-structure.md b/src/content/learn/choosing-the-state-structure.md index b1478324e8..8c49e66a8f 100644 --- a/src/content/learn/choosing-the-state-structure.md +++ b/src/content/learn/choosing-the-state-structure.md @@ -1,53 +1,56 @@ --- -title: Choosing the State Structure +title: 选择状态结构 +translators: + - Davont --- -Structuring state well can make a difference between a component that is pleasant to modify and debug, and one that is a constant source of bugs. Here are some tips you should consider when structuring state. +构建良好的状态可以让组件变得易于修改和调试,而不是成为一个经常出错的组件。以下是你在构建状态时应该考虑的一些建议。 -* When to use a single vs multiple state variables -* What to avoid when organizing state -* How to fix common issues with the state structure +* 何时使用单个 state 变量和多个 state 变量 +* 组织状态时应避免的内容 +* 如何解决状态结构中的常见问题 -## Principles for structuring state {/*principles-for-structuring-state*/} +## 构建状态的原则 {/*principles-for-structuring-state*/} -When you write a component that holds some state, you'll have to make choices about how many state variables to use and what the shape of their data should be. While it's possible to write correct programs even with a suboptimal state structure, there are a few principles that can guide you to make better choices: +当你编写一个存有 state 的组件时,你需要选择使用多少个 state 变量以及它们都是怎样的数据格式。尽管选择次优的 state 结构下也可以编写正确的程序,但有几个原则可以指导您做出更好的决策: -1. **Group related state.** If you always update two or more state variables at the same time, consider merging them into a single state variable. -2. **Avoid contradictions in state.** When the state is structured in a way that several pieces of state may contradict and "disagree" with each other, you leave room for mistakes. Try to avoid this. -3. **Avoid redundant state.** If you can calculate some information from the component's props or its existing state variables during rendering, you should not put that information into that component's state. -4. **Avoid duplication in state.** When the same data is duplicated between multiple state variables, or within nested objects, it is difficult to keep them in sync. Reduce duplication when you can. -5. **Avoid deeply nested state.** Deeply hierarchical state is not very convenient to update. When possible, prefer to structure state in a flat way. +1. **合并关联的状态。** 如果你总是同时更新两个或更多的 state 变量,请考虑将它们合并为一个单独的 state 变量。 +2. **避免互相矛盾的状态。** 当状态结构中存在多个相互矛盾或“不一致”的 state 时,你就可能为此会留下隐患。应尽量避免这种情况。 +3. **避免冗余的状态。** 如果你能在渲染期间从组件的 props 或其现有的 state 变量中计算出一些信息,则不应将这些信息放入该组件的 state 中。 +4. **避免重复的状态。** 当同一数据在多个 state 变量之间或在多个嵌套对象中重复时,这会很难保持它们同步。应尽可能减少重复。 +5. **避免深度嵌套的状态。** 深度分层的状态更新起来不是很方便。如果可能的话,最好以扁平化方式构建状态。 -The goal behind these principles is to *make state easy to update without introducing mistakes*. Removing redundant and duplicate data from state helps ensure that all its pieces stay in sync. This is similar to how a database engineer might want to ["normalize" the database structure](https://docs.microsoft.com/en-us/office/troubleshoot/access/database-normalization-description) to reduce the chance of bugs. To paraphrase Albert Einstein, **"Make your state as simple as it can be--but no simpler."** +这些原则背后的目标是*使状态易于更新而不引入错误*。从状态中删除冗余和重复数据有助于确保所有部分保持同步。这类似于数据库工程师想要 [“规范化”数据库结构](https://docs.microsoft.com/en-us/office/troubleshoot/access/database-normalization-description),以减少出现错误的机会。用爱因斯坦的话说,**“让你的状态尽可能简单,但不要过于简单。”** -Now let's see how these principles apply in action. +现在让我们来看看这些原则在实际中是如何应用的。 -## Group related state {/*group-related-state*/} +## 合并关联的状态 {/*group-related-state*/} -You might sometimes be unsure between using a single or multiple state variables. +有时候你可能会不确定是使用单个 state 变量还是多个 state 变量。 -Should you do this? +你会像下面这样做吗? ```js const [x, setX] = useState(0); const [y, setY] = useState(0); ``` -Or this? +或这样? ```js const [position, setPosition] = useState({ x: 0, y: 0 }); ``` -Technically, you can use either of these approaches. But **if some two state variables always change together, it might be a good idea to unify them into a single state variable.** Then you won't forget to always keep them in sync, like in this example where moving the cursor updates both coordinates of the red dot: + +从技术上讲,你可以使用其中任何一种方法。但是,**如果某两个 state 变量总是一起变化,则将它们统一成一个 state 变量可能更好**。这样你就不会忘记让它们始终保持同步,就像下面这个例子中,移动光标会同时更新红点的两个坐标: @@ -93,17 +96,17 @@ body { margin: 0; padding: 0; height: 250px; } -Another case where you'll group data into an object or an array is when you don't know how many pieces of state you'll need. For example, it's helpful when you have a form where the user can add custom fields. +另一种情况是,你将数据整合到一个对象或一个数组中时,你不知道需要多少个 state 片段。例如,当你有一个用户可以添加自定义字段的表单时,这将会很有帮助。 -If your state variable is an object, remember that [you can't update only one field in it](/learn/updating-objects-in-state) without explicitly copying the other fields. For example, you can't do `setPosition({ x: 100 })` in the above example because it would not have the `y` property at all! Instead, if you wanted to set `x` alone, you would either do `setPosition({ ...position, x: 100 })`, or split them into two state variables and do `setX(100)`. +如果你的 state 变量是一个对象时,请记住,[你不能只更新其中的一个字段](/learn/updating-objects-in-state) 而不显式复制其他字段。例如,在上面的例子中,你不能写成 `setPosition({ x: 100 })`,因为它根本就没有 `y` 属性! 相反,如果你想要仅设置 `x`,则可执行 `setPosition({ ...position, x: 100 })`,或将它们分成两个 state 变量,并执行 `setX(100)`。 -## Avoid contradictions in state {/*avoid-contradictions-in-state*/} +## 避免矛盾的状态 {/*avoid-contradictions-in-state*/} -Here is a hotel feedback form with `isSending` and `isSent` state variables: +下面是带有 `isSending` 和 `isSent` 两个 state 变量的酒店反馈表单: @@ -147,7 +150,7 @@ export default function FeedbackForm() { ); } -// Pretend to send a message. +// 假装发送一条消息。 function sendMessage(text) { return new Promise(resolve => { setTimeout(resolve, 2000); @@ -157,9 +160,9 @@ function sendMessage(text) { -While this code works, it leaves the door open for "impossible" states. For example, if you forget to call `setIsSent` and `setIsSending` together, you may end up in a situation where both `isSending` and `isSent` are `true` at the same time. The more complex your component is, the harder it is to understand what happened. +尽管这段代码是有效的,但也会让一些状态“极难处理”。例如,如果你忘记同时调用 `setIsSent` 和 `setIsSending`,则可能会出现 `isSending` 和 `isSent` 同时为 `true` 的情况。你的组件越复杂,你就越难理解发生了什么。 -**Since `isSending` and `isSent` should never be `true` at the same time, it is better to replace them with one `status` state variable that may take one of *three* valid states:** `'typing'` (initial), `'sending'`, and `'sent'`: +**因为 `isSending` 和 `isSent` 不应同时为 `true`,所以最好用一个 `status` 变量来代替它们,这个 state 变量可以采取 *三种* 有效状态其中之一:**`'typing'` (初始), `'sending'`, 和 `'sent'`: @@ -204,7 +207,7 @@ export default function FeedbackForm() { ); } -// Pretend to send a message. +// 假装发送一条消息。 function sendMessage(text) { return new Promise(resolve => { setTimeout(resolve, 2000); @@ -214,20 +217,20 @@ function sendMessage(text) { -You can still declare some constants for readability: +你仍然可以声明一些常量,以提高可读性: ```js const isSending = status === 'sending'; const isSent = status === 'sent'; ``` -But they're not state variables, so you don't need to worry about them getting out of sync with each other. +但它们不是 state 变量,所以你不必担心它们彼此失去同步。 -## Avoid redundant state {/*avoid-redundant-state*/} +## 避免冗余的状态 {/*avoid-redundant-state*/} -If you can calculate some information from the component's props or its existing state variables during rendering, you **should not** put that information into that component's state. +如果你能在渲染期间从组件的 props 或其现有的 state 变量中计算出一些信息,则不应该把这些信息放到该组件的状态中。 -For example, take this form. It works, but can you find any redundant state in it? +例如,以这个表单为例。它可以运行,但你能找到其中任何冗余的 state 吗? @@ -280,9 +283,9 @@ label { display: block; margin-bottom: 5px; } -This form has three state variables: `firstName`, `lastName`, and `fullName`. However, `fullName` is redundant. **You can always calculate `fullName` from `firstName` and `lastName` during render, so remove it from state.** +这个表单有三个 state 变量:`firstName`、`lastName` 和 `fullName`。然而,`fullName` 是多余的。**在渲染期间,你始终可以从 `firstName` 和 `lastName` 中计算出 `fullName`,因此需要把它从 state 中删除。** -This is how you can do it: +你可以这样做: @@ -334,50 +337,53 @@ label { display: block; margin-bottom: 5px; } -Here, `fullName` is *not* a state variable. Instead, it's calculated during render: +这里,`fullName` *不是* 一个 state 变量。相反,它是在渲染期间中计算出的: ```js const fullName = firstName + ' ' + lastName; ``` -As a result, the change handlers don't need to do anything special to update it. When you call `setFirstName` or `setLastName`, you trigger a re-render, and then the next `fullName` will be calculated from the fresh data. +因此,更改处理程序不需要做任何特殊操作来更新它。 当你调用 `setFirstName` 或 `setLastName` 时,你会触发一次重新渲染,然后下一个 `fullName` 将从新数据中计算出来。。 -#### Don't mirror props in state {/*don-t-mirror-props-in-state*/} +#### 不要在 state 中镜像 props {/*don-t-mirror-props-in-state*/} -A common example of redundant state is code like this: +以下代码是体现 state 冗余的一个常见例子: ```js function Message({ messageColor }) { const [color, setColor] = useState(messageColor); ``` -Here, a `color` state variable is initialized to the `messageColor` prop. The problem is that **if the parent component passes a different value of `messageColor` later (for example, `'red'` instead of `'blue'`), the `color` *state variable* would not be updated!** The state is only initialized during the first render. +这里,一个 `color` state 变量被初始化为 `messageColor` 的 props 值。 这段代码的问题在于,**如果父组件稍后传递不同的 `messageColor` 值(例如,将其从 `'blue'` 更改为 `'red'`),则`color` *state 变量*将不会更新!** state 仅在第一次渲染期间初始化。 -This is why "mirroring" some prop in a state variable can lead to confusion. Instead, use the `messageColor` prop directly in your code. If you want to give it a shorter name, use a constant: +这就是为什么在 state 变量中,"镜像"一些 prop 属性会导致混淆的原因。相反,你要在代码中直接使用 `messageColor` 属性。如果你想给它起一个更短的名称,请使用常量: ```js function Message({ messageColor }) { const color = messageColor; ``` -This way it won't get out of sync with the prop passed from the parent component. +这种写法就不会与从父组件传递的属性失去同步。 + +只有当你 *想要* 忽略特定 props 属性的所有更新时,将 props “镜像”到 state 才有意义。按照惯例,prop 名称以 `initial` 或 `default` 开头,以阐明该 prop 的新值将被忽略: -"Mirroring" props into state only makes sense when you *want* to ignore all updates for a specific prop. By convention, start the prop name with `initial` or `default` to clarify that its new values are ignored: + +这个 `color` state 变量用于保存 `initialColor` 的 *初始值* 值。 ```js function Message({ initialColor }) { - // The `color` state variable holds the *first* value of `initialColor`. - // Further changes to the `initialColor` prop are ignored. + // 这个 `color` state 变量用于保存 `initialColor` 的 *初始值* 值。 + // 对于 `initialColor` 属性的进一步更改将被忽略。 const [color, setColor] = useState(initialColor); ``` -## Avoid duplication in state {/*avoid-duplication-in-state*/} +## 避免重复的状态 {/*avoid-duplication-in-state*/} -This menu list component lets you choose a single travel snack out of several: +下面这个菜单列表组件可以让你在多种旅行小吃中选择一个: @@ -422,9 +428,9 @@ button { margin-top: 10px; } -Currently, it stores the selected item as an object in the `selectedItem` state variable. However, this is not great: **the contents of the `selectedItem` is the same object as one of the items inside the `items` list.** This means that the information about the item itself is duplicated in two places. +当前,它将所选菜作为对象存储在 `selectedItem` state 变量中。然而,这并不好:**`selectedItem` 的内容与 `items` 列表中的某个项是同一个对象。** 这意味着关于该项本身的信息在两个地方产生了重复。 -Why is this a problem? Let's make each item editable: +为什么这是个问题? 让我们使每个项目都可以编辑: @@ -487,9 +493,9 @@ button { margin-top: 10px; } -Notice how if you first click "Choose" on an item and *then* edit it, **the input updates but the label at the bottom does not reflect the edits.** This is because you have duplicated state, and you forgot to update `selectedItem`. +请注意,如果你首先单击菜单上的“Choose” *然后* 编辑它,**输入会更新,但底部的标签不会反映编辑内容。** 这是因为你有重复的 state,并且你忘记更新了 `selectedItem`。 -Although you could update `selectedItem` too, an easier fix is to remove duplication. In this example, instead of a `selectedItem` object (which creates a duplication with objects inside `items`), you hold the `selectedId` in state, and *then* get the `selectedItem` by searching the `items` array for an item with that ID: +尽管你也可以更新 `selectedItem`,但更简单的解决方法是消除重复项。在下面这个例子中,你将 `selectedId` 保存在 state 中,而不是在 `selectedItem` 对象中(它创建了一个与 `items` 内重复的对象),*然后* 通过搜索 `items` 数组中具有该 ID 的项,以此获取 `selectedItem`: @@ -554,25 +560,26 @@ button { margin-top: 10px; } -(Alternatively, you may hold the selected index in state.) +(或者,你可以将所选索引保持在 state 中。) -The state used to be duplicated like this: +state 过去常常是这样复制的: * `items = [{ id: 0, title: 'pretzels'}, ...]` * `selectedItem = {id: 0, title: 'pretzels'}` -But after the change it's like this: +改了之后是这样的: * `items = [{ id: 0, title: 'pretzels'}, ...]` * `selectedId = 0` -The duplication is gone, and you only keep the essential state! +重复的 state 没有了,你只保留了必要的 state! + -Now if you edit the *selected* item, the message below will update immediately. This is because `setItems` triggers a re-render, and `items.find(...)` would find the item with the updated title. You didn't need to hold *the selected item* in state, because only the *selected ID* is essential. The rest could be calculated during render. +现在,如果你编辑 *selected* 项目,下面的消息将立即更新。这是因为 `setItems` 会触发重新渲染,而 `items.find(...)` 会找到带有更新文本的项目。你不需要在 state 中保存 *选定的项目* ,因为只有 *选定的 ID* 是必要的。其余的可以在渲染期间计算。 -## Avoid deeply nested state {/*avoid-deeply-nested-state*/} +## 避免深度嵌套的状态 {/*avoid-deeply-nested-state*/} -Imagine a travel plan consisting of planets, continents, and countries. You might be tempted to structure its state using nested objects and arrays, like in this example: +想象一下,一个由行星、大陆和国家组成的旅行计划。你可能会尝试使用嵌套对象和数组来构建它的状态,就像下面这个例子: @@ -818,11 +825,11 @@ export const initialTravelPlan = { -Now let's say you want to add a button to delete a place you've already visited. How would you go about it? [Updating nested state](/learn/updating-objects-in-state#updating-a-nested-object) involves making copies of objects all the way up from the part that changed. Deleting a deeply nested place would involve copying its entire parent place chain. Such code can be very verbose. +现在,假设你想添加一个按钮来删除一个你已经去过的地方。你会怎么做呢?[更新嵌套的 state](/learn/updating-objects-in-state#updating-a-nested-object) 需要从更改部分一直向上复制对象。删除一个深度嵌套的地点将涉及复制其整个父级地点链。这样的代码可能非常冗长。 -**If the state is too nested to update easily, consider making it "flat".** Here is one way you can restructure this data. Instead of a tree-like structure where each `place` has an array of *its child places*, you can have each place hold an array of *its child place IDs*. Then store a mapping from each place ID to the corresponding place. +**如果 state 嵌套太深,难以轻松更新,可以考虑将其“扁平化”。** 这里有一个方法可以重构上面这个数据。不同于树状结构,它每个`节点`都有 *其子节点* 数组,你可以让每个`节点`保存一个 *其子节点ID* 的数组。然后存储一个节点 ID 与相应节点的映射关系。 -This data restructuring might remind you of seeing a database table: +这个数据重组可能会让你想起看到一个数据库表: @@ -1129,14 +1136,17 @@ export const initialTravelPlan = { -**Now that the state is "flat" (also known as "normalized"), updating nested items becomes easier.** +**现在 state 已经“扁平化”(也称为“规范化”),更新嵌套项会变得更加容易。** + +现在要删除一个地点,您只需要更新两个状态级别: + + -In order to remove a place now, you only need to update two levels of state: -- The updated version of its *parent* place should exclude the removed ID from its `childIds` array. -- The updated version of the root "table" object should include the updated version of the parent place. +- 其 *父级* 地点的更新版本应该从其 `childIds` 数组中排除已删除的ID。 +- 其根级“表”对象的更新版本应包括父级地点的更新版本。 -Here is an example of how you could go about it: +下面是展示如何处理它的一个示例: @@ -1149,17 +1159,17 @@ export default function TravelPlan() { function handleComplete(parentId, childId) { const parent = plan[parentId]; - // Create a new version of the parent place - // that doesn't include this child ID. + // 创建一个其父级地点的新版本 + // 但不包括子级 ID。 const nextParent = { ...parent, childIds: parent.childIds .filter(id => id !== childId) }; - // Update the root state object... + // 更新根 state 对象... setPlan({ ...plan, - // ...so that it has the updated parent. + // ...以便它拥有更新的父级。 [parentId]: nextParent }); } @@ -1474,13 +1484,13 @@ button { margin: 10px; } -You can nest state as much as you like, but making it "flat" can solve numerous problems. It makes state easier to update, and it helps ensure you don't have duplication in different parts of a nested object. +你确实可以随心所欲地嵌套状态,但是将其“扁平化”可以解决许多问题。这使得状态更容易更新,并且有助于确保在嵌套对象的不同部分中没有重复。 -#### Improving memory usage {/*improving-memory-usage*/} +#### 改善内存使用 {/*improving-memory-usage*/} -Ideally, you would also remove the deleted items (and their children!) from the "table" object to improve memory usage. This version does that. It also [uses Immer](/learn/updating-objects-in-state#write-concise-update-logic-with-immer) to make the update logic more concise. +理想情况下,您还应该从“表”对象中删除已删除的项目(以及它们的子项!)以改善内存使用。还可以 [使用Immer](/learn/updating-objects-in-state#write-concise-update-logic-with-immer) 使更新逻辑更加简洁。 @@ -1493,12 +1503,12 @@ export default function TravelPlan() { function handleComplete(parentId, childId) { updatePlan(draft => { - // Remove from the parent place's child IDs. + // 从父级地点的子 ID 中移除。 const parent = draft[parentId]; parent.childIds = parent.childIds .filter(id => id !== childId); - // Forget this place and all its subtree. + // 删除这个地点和它的所有子目录。 deleteAllChildren(childId); function deleteAllChildren(id) { const place = draft[id]; @@ -1838,25 +1848,25 @@ button { margin: 10px; } -Sometimes, you can also reduce state nesting by moving some of the nested state into the child components. This works well for ephemeral UI state that doesn't need to be stored, like whether an item is hovered. +有时候,你也可以通过将一些嵌套状态移动到子组件中来减少状态的嵌套。这对于不需要保存的短暂 UI 状态非常有效,比如一个选项是否被悬停。 -* If two state variables always update together, consider merging them into one. -* Choose your state variables carefully to avoid creating "impossible" states. -* Structure your state in a way that reduces the chances that you'll make a mistake updating it. -* Avoid redundant and duplicate state so that you don't need to keep it in sync. -* Don't put props *into* state unless you specifically want to prevent updates. -* For UI patterns like selection, keep ID or index in state instead of the object itself. -* If updating deeply nested state is complicated, try flattening it. +* 如果两个 state 变量总是一起更新,请考虑将它们合并为一个。 +* 仔细选择你的 state 变量,以避免创建“极难处理”的状态。 +* 用一种减少出错更新的机会的方式来构建你的状态。 +* 避免冗余和重复的状态,这样您就不需要保持同步。 +* 除非您特别想防止更新,否则不要将 props *放入* state中。 +* 对于选择类型的 UI 模式,请在 state 中保存 ID 或索引而不是对象本身。 +* 如果深度嵌套状态更新很复杂,请尝试将其展开扁平化。 -#### Fix a component that's not updating {/*fix-a-component-thats-not-updating*/} +#### 修复一个未更新的组件 {/*fix-a-component-thats-not-updating*/} -This `Clock` component receives two props: `color` and `time`. When you select a different color in the select box, the `Clock` component receives a different `color` prop from its parent component. However, for some reason, the displayed color doesn't update. Why? Fix the problem. +这个 `Clock` 组件接收两个属性:`color` 和 `time`。当您在选择框中选择不同的颜色时,`Clock` 组件将从其父组件接收到一个不同的 `color` 属性。然而,由于某种原因,显示的颜色没有更新。为什么?请修复这个问题。 @@ -1911,7 +1921,7 @@ export default function App() { -The issue is that this component has `color` state initialized with the initial value of the `color` prop. But when the `color` prop changes, this does not affect the state variable! So they get out of sync. To fix this issue, remove the state variable altogether, and use the `color` prop directly. +这个问题点在于此组件的 `color` 状态是使用`color` prop 的初始值进行初始化的。但是当 `color` prop 值发生更改时,这不会影响 state 变量!因此它们会失去同步。为了解决这个问题,完全删除 state 变量,并直接使用 `color` prop 即可。 @@ -1963,7 +1973,7 @@ export default function App() { -Or, using the destructuring syntax: +或者,使用解构语法: @@ -2017,13 +2027,13 @@ export default function App() { -#### Fix a broken packing list {/*fix-a-broken-packing-list*/} +#### 修复一个损坏的打包清单 {/*fix-a-broken-packing-list*/} -This packing list has a footer that shows how many items are packed, and how many items there are overall. It seems to work at first, but it is buggy. For example, if you mark an item as packed and then delete it, the counter will not be updated correctly. Fix the counter so that it's always correct. +这个打包清单有一个页脚,显示了打包的物品数量和总共的物品数量。一开始看起来似乎很好用,但是它也存在漏斗。例如,如果你将一个物品标记为已打包然后删除它,计数器就不会正确更新。请修复计数器以使其始终正确。 -Is any state in this example redundant? +在这个例子中,是否有 state 是多余的? @@ -2164,7 +2174,7 @@ ul, li { margin: 0; padding: 0; } -Although you could carefully change each event handler to update the `total` and `packed` counters correctly, the root problem is that these state variables exist at all. They are redundant because you can always calculate the number of items (packed or total) from the `items` array itself. Remove the redundant state to fix the bug: +虽然你可以仔细更改每个事件处理程序来正确更新 `total` 和 `packed` 计数器,但根本问题在于这些 state 变量一直存在。它们是冗余的,因为你始终可以从 `item` 数组本身计算出物品(已打包或总共)的数量。因此需要删除冗余状态以修复错误: @@ -2297,15 +2307,15 @@ ul, li { margin: 0; padding: 0; } -Notice how the event handlers are only concerned with calling `setItems` after this change. The item counts are now calculated during the next render from `items`, so they are always up-to-date. +请注意,事件处理程序在这次更改后只关心调用 `setItems`。现在,项目计数是从 `items` 中在下一次渲染期间计算的,因此它们始终是最新的。 -#### Fix the disappearing selection {/*fix-the-disappearing-selection*/} +#### 修复消失的选项 {/*fix-the-disappearing-selection*/} -There is a list of `letters` in state. When you hover or focus a particular letter, it gets highlighted. The currently highlighted letter is stored in the `highlightedLetter` state variable. You can "star" and "unstar" individual letters, which updates the `letters` array in state. +有一个 `letters` 列表在状态中。当你悬停或聚焦到特定的字母时,它会被突出显示。当前突出显示的字母存储在 `highlightedLetter` state 变量中。您可以 “Star” 和 “Unstar” 单个字母,这将更新 state 中的 `letters` 数组。 -This code works, but there is a minor UI glitch. When you press "Star" or "Unstar", the highlighting disappears for a moment. However, it reappears as soon as you move your pointer or switch to another letter with keyboard. Why is this happening? Fix it so that the highlighting doesn't disappear after the button click. +虽然这段代码可以运行,但是有一个小的 UI 问题。当你点击 “Star” 或 “Unstar” 时,高亮会短暂消失。不过只要你移动鼠标指针或者用键盘切换到另一个字母,它就会重新出现。为什么会这样?请修复它,使得在按钮点击后高亮不会消失。 @@ -2412,9 +2422,9 @@ li { border-radius: 5px; } -The problem is that you're holding the letter object in `highlightedLetter`. But you're also holding the same information in the `letters` array. So your state has duplication! When you update the `letters` array after the button click, you create a new letter object which is different from `highlightedLetter`. This is why `highlightedLetter === letter` check becomes `false`, and the highlight disappears. It reappears the next time you call `setHighlightedLetter` when the pointer moves. +这个问题点在于你将字母对象存储在 `highlightedLetter` 中。但是,你也将相同的信息存储在 `letters` 数组中。因此,你的状态存在重复!当你在按钮点击后更新 `letters` 数组时,会创建一个新的字母对象,它与 `highlightedLetter` 不同。这就是为什么 `highlightedLetter === letter` 执行变为 `false`,并且高亮消失的原因。当指针移动时下一次调用 `setHighlightedLetter` 时它会重新出现。 -To fix the issue, remove the duplication from state. Instead of storing *the letter itself* in two places, store the `highlightedId` instead. Then you can check `isHighlighted` for each letter with `letter.id === highlightedId`, which will work even if the `letter` object has changed since the last render. +为了解决这个问题,请从 state 中删除重复项。不要在两个地方存储 `字母对象本身`,而是存储 `highlightedId`。然后,您可以使用 `letter.id === highlightedId` 检查每个带有 `isHighlighted` 属性的字母,即使 `letter` 对象在上次渲染后发生了变化,这也是可行的。 @@ -2521,15 +2531,15 @@ li { border-radius: 5px; } -#### Implement multiple selection {/*implement-multiple-selection*/} +#### 实现多选功能 {/*implement-multiple-selection*/} -In this example, each `Letter` has an `isSelected` prop and an `onToggle` handler that marks it as selected. This works, but the state is stored as a `selectedId` (either `null` or an ID), so only one letter can get selected at any given time. +在这个例子中,每个 `Letter` 都有一个 `isSelected` prop 和一个 `onToggle` 处理程序来标记它为选定状态。这样做是有效的,但是 state 被存储为 `selectedId`(也可以是 `null` 或 `ID`),因此任何时候只能选择一个 letter。 -Change the state structure to support multiple selection. (How would you structure it? Think about this before writing the code.) Each checkbox should become independent from the others. Clicking a selected letter should uncheck it. Finally, the footer should show the correct number of the selected items. +你需要将 state 结构更改为支持多选功能。(在编写代码之前,请考虑如何构建它。)每个复选框应该独立于其他复选框。单击已选择的项目应取消选择。最后,页脚应显示所选项目的正确数量。 -Instead of a single selected ID, you might want to hold an array or a [Set](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set) of selected IDs in state. +你可以在 state 中保存一个选定 ID 的数组或 [Set](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Set),而不是单个选定的 ID。 @@ -2543,11 +2553,11 @@ import Letter from './Letter.js'; export default function MailClient() { const [selectedId, setSelectedId] = useState(null); - // TODO: allow multiple selection + // TODO: 支持多选 const selectedCount = 1; function handleToggle(toggledId) { - // TODO: allow multiple selection + // TODO: 支持多选 setSelectedId(toggledId); } @@ -2560,7 +2570,7 @@ export default function MailClient() { key={letter.id} letter={letter} isSelected={ - // TODO: allow multiple selection + // TODO: 支持多选 letter.id === selectedId } onToggle={handleToggle} @@ -2630,7 +2640,7 @@ label { width: 100%; padding: 5px; display: inline-block; } -Instead of a single `selectedId`, keep a `selectedIds` *array* in state. For example, if you select the first and the last letter, it would contain `[0, 2]`. When nothing is selected, it would be an empty `[]` array: +在状态中保留一个 `selectedIds` *数组*,而不是单个的 `selectedId`。例如,如果您选择了第一个和最后一个字母,则它将包含 `[0, 2]`。当没有选定任何内容时,它将为空数组 `[]`: @@ -2645,14 +2655,14 @@ export default function MailClient() { const selectedCount = selectedIds.length; function handleToggle(toggledId) { - // Was it previously selected? + // 它以前是被选中的吗? if (selectedIds.includes(toggledId)) { // Then remove this ID from the array. setSelectedIds(selectedIds.filter(id => id !== toggledId )); } else { - // Otherwise, add this ID to the array. + // 否则,增加 ID 到数组中。 setSelectedIds([ ...selectedIds, toggledId @@ -2736,9 +2746,9 @@ label { width: 100%; padding: 5px; display: inline-block; } -One minor downside of using an array is that for each item, you're calling `selectedIds.includes(letter.id)` to check whether it's selected. If the array is very large, this can become a performance problem because array search with [`includes()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/includes) takes linear time, and you're doing this search for each individual item. +使用数组的一个小缺点是,对于每个项目,你都需要调用 `selectedIds.includes(letter.id)` 来检查它是否被选中。如果数组非常大,则这可能会成为性能问题,因为带有 [`includes()`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/includes) 的数组搜索需要线性时间,并且你正在为每个单独的项目执行此搜索。 -To fix this, you can hold a [Set](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set) in state instead, which provides a fast [`has()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/has) operation: +要解决这个问题,你可以在状态中使用一个 [Set](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Set) 对象,它提供了快速的 [`has()`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Set/has) 操作: @@ -2841,9 +2851,9 @@ label { width: 100%; padding: 5px; display: inline-block; } -Now each item does a `selectedIds.has(letter.id)` check, which is very fast. +现在每个项目都会进行 `selectedIds.has(letter.id)` 检查,这非常快。 -Keep in mind that you [should not mutate objects in state](/learn/updating-objects-in-state), and that includes Sets, too. This is why the `handleToggle` function creates a *copy* of the Set first, and then updates that copy. +请记住,你[不应该在状态中改变对象](/learn/updating-objects-in-state),包括 Set 中。这就是为什么 `handleToggle` 函数首先创建 Set 的*副本*,然后更新该副本的原因。 From 7c3fb5fb394c4c78c1fb42d1409c2e2fb8cf5ce4 Mon Sep 17 00:00:00 2001 From: davont Date: Thu, 23 Mar 2023 21:00:09 +0800 Subject: [PATCH 02/30] docs(cn): tupdate sidebar translations --- src/sidebarLearn.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sidebarLearn.json b/src/sidebarLearn.json index 0df8c6960b..0749ddf077 100644 --- a/src/sidebarLearn.json +++ b/src/sidebarLearn.json @@ -130,7 +130,7 @@ "path": "/learn/reacting-to-input-with-state" }, { - "title": "Choosing the State Structure", + "title": "选择状态结构", "path": "/learn/choosing-the-state-structure" }, { @@ -186,7 +186,7 @@ }, { "title": "Removing Effect Dependencies", - "path": "/learn/removing-effect-dependencies" + "path": "/learn/removing-effect-dependencies" }, { "title": "Reusing Logic with Custom Hooks", From ed691dd1e81d59c68fd87fb90d0743743d9c3227 Mon Sep 17 00:00:00 2001 From: davont Date: Thu, 23 Mar 2023 21:38:19 +0800 Subject: [PATCH 03/30] docs(cn): translate the choosing-the-state-structure page --- .../learn/choosing-the-state-structure.md | 68 +++++++++---------- 1 file changed, 34 insertions(+), 34 deletions(-) diff --git a/src/content/learn/choosing-the-state-structure.md b/src/content/learn/choosing-the-state-structure.md index 8c49e66a8f..d09dd9d71a 100644 --- a/src/content/learn/choosing-the-state-structure.md +++ b/src/content/learn/choosing-the-state-structure.md @@ -1,38 +1,38 @@ --- -title: 选择状态结构 +title: 选择 State 结构 translators: - Davont --- -构建良好的状态可以让组件变得易于修改和调试,而不是成为一个经常出错的组件。以下是你在构建状态时应该考虑的一些建议。 +构建良好的 state 可以让组件变得易于修改和调试,而不是成为一个经常出错的组件。以下是你在构建 state 时应该考虑的一些建议。 * 何时使用单个 state 变量和多个 state 变量 -* 组织状态时应避免的内容 -* 如何解决状态结构中的常见问题 +* 组织 state 时应避免的内容 +* 如何解决 state 结构中的常见问题 -## 构建状态的原则 {/*principles-for-structuring-state*/} +## 构建 state 的原则 {/*principles-for-structuring-state*/} 当你编写一个存有 state 的组件时,你需要选择使用多少个 state 变量以及它们都是怎样的数据格式。尽管选择次优的 state 结构下也可以编写正确的程序,但有几个原则可以指导您做出更好的决策: -1. **合并关联的状态。** 如果你总是同时更新两个或更多的 state 变量,请考虑将它们合并为一个单独的 state 变量。 -2. **避免互相矛盾的状态。** 当状态结构中存在多个相互矛盾或“不一致”的 state 时,你就可能为此会留下隐患。应尽量避免这种情况。 -3. **避免冗余的状态。** 如果你能在渲染期间从组件的 props 或其现有的 state 变量中计算出一些信息,则不应将这些信息放入该组件的 state 中。 -4. **避免重复的状态。** 当同一数据在多个 state 变量之间或在多个嵌套对象中重复时,这会很难保持它们同步。应尽可能减少重复。 -5. **避免深度嵌套的状态。** 深度分层的状态更新起来不是很方便。如果可能的话,最好以扁平化方式构建状态。 +1. **合并关联的 state。** 如果你总是同时更新两个或更多的 state 变量,请考虑将它们合并为一个单独的 state 变量。 +2. **避免互相矛盾的 state。** 当 state 结构中存在多个相互矛盾或“不一致”的 state 时,你就可能为此会留下隐患。应尽量避免这种情况。 +3. **避免冗余的 state。** 如果你能在渲染期间从组件的 props 或其现有的 state 变量中计算出一些信息,则不应将这些信息放入该组件的 state 中。 +4. **避免重复的 state。** 当同一数据在多个 state 变量之间或在多个嵌套对象中重复时,这会很难保持它们同步。应尽可能减少重复。 +5. **避免深度嵌套的 state。** 深度分层的 state 更新起来不是很方便。如果可能的话,最好以扁平化方式构建 state 。 -这些原则背后的目标是*使状态易于更新而不引入错误*。从状态中删除冗余和重复数据有助于确保所有部分保持同步。这类似于数据库工程师想要 [“规范化”数据库结构](https://docs.microsoft.com/en-us/office/troubleshoot/access/database-normalization-description),以减少出现错误的机会。用爱因斯坦的话说,**“让你的状态尽可能简单,但不要过于简单。”** +这些原则背后的目标是*使 state 易于更新而不引入错误*。从 state 中删除冗余和重复数据有助于确保所有部分保持同步。这类似于数据库工程师想要 [“规范化”数据库结构](https://docs.microsoft.com/en-us/office/troubleshoot/access/database-normalization-description),以减少出现错误的机会。用爱因斯坦的话说,**“让你的状态尽可能简单,但不要过于简单。”** 现在让我们来看看这些原则在实际中是如何应用的。 -## 合并关联的状态 {/*group-related-state*/} +## 合并关联的 state {/*group-related-state*/} 有时候你可能会不确定是使用单个 state 变量还是多个 state 变量。 @@ -104,7 +104,7 @@ body { margin: 0; padding: 0; height: 250px; } -## 避免矛盾的状态 {/*avoid-contradictions-in-state*/} +## 避免矛盾的 state {/*avoid-contradictions-in-state*/} 下面是带有 `isSending` 和 `isSent` 两个 state 变量的酒店反馈表单: @@ -160,7 +160,7 @@ function sendMessage(text) { -尽管这段代码是有效的,但也会让一些状态“极难处理”。例如,如果你忘记同时调用 `setIsSent` 和 `setIsSending`,则可能会出现 `isSending` 和 `isSent` 同时为 `true` 的情况。你的组件越复杂,你就越难理解发生了什么。 +尽管这段代码是有效的,但也会让一些 state “极难处理”。例如,如果你忘记同时调用 `setIsSent` 和 `setIsSending`,则可能会出现 `isSending` 和 `isSent` 同时为 `true` 的情况。你的组件越复杂,你就越难理解发生了什么。 **因为 `isSending` 和 `isSent` 不应同时为 `true`,所以最好用一个 `status` 变量来代替它们,这个 state 变量可以采取 *三种* 有效状态其中之一:**`'typing'` (初始), `'sending'`, 和 `'sent'`: @@ -226,9 +226,9 @@ const isSent = status === 'sent'; 但它们不是 state 变量,所以你不必担心它们彼此失去同步。 -## 避免冗余的状态 {/*avoid-redundant-state*/} +## 避免冗余的 state {/*avoid-redundant-state*/} -如果你能在渲染期间从组件的 props 或其现有的 state 变量中计算出一些信息,则不应该把这些信息放到该组件的状态中。 +如果你能在渲染期间从组件的 props 或其现有的 state 变量中计算出一些信息,则不应该把这些信息放到该组件的 state 中。 例如,以这个表单为例。它可以运行,但你能找到其中任何冗余的 state 吗? @@ -381,7 +381,7 @@ function Message({ initialColor }) { -## 避免重复的状态 {/*avoid-duplication-in-state*/} +## 避免重复的 state {/*avoid-duplication-in-state*/} 下面这个菜单列表组件可以让你在多种旅行小吃中选择一个: @@ -577,9 +577,9 @@ state 过去常常是这样复制的: 现在,如果你编辑 *selected* 项目,下面的消息将立即更新。这是因为 `setItems` 会触发重新渲染,而 `items.find(...)` 会找到带有更新文本的项目。你不需要在 state 中保存 *选定的项目* ,因为只有 *选定的 ID* 是必要的。其余的可以在渲染期间计算。 -## 避免深度嵌套的状态 {/*avoid-deeply-nested-state*/} +## 避免深度嵌套的 state {/*avoid-deeply-nested-state*/} -想象一下,一个由行星、大陆和国家组成的旅行计划。你可能会尝试使用嵌套对象和数组来构建它的状态,就像下面这个例子: +想象一下,一个由行星、大陆和国家组成的旅行计划。你可能会尝试使用嵌套对象和数组来构建它的 state,就像下面这个例子: @@ -1138,7 +1138,7 @@ export const initialTravelPlan = { **现在 state 已经“扁平化”(也称为“规范化”),更新嵌套项会变得更加容易。** -现在要删除一个地点,您只需要更新两个状态级别: +现在要删除一个地点,您只需要更新两个 state 级别: @@ -1484,7 +1484,7 @@ button { margin: 10px; } -你确实可以随心所欲地嵌套状态,但是将其“扁平化”可以解决许多问题。这使得状态更容易更新,并且有助于确保在嵌套对象的不同部分中没有重复。 +你确实可以随心所欲地嵌套 state,但是将其“扁平化”可以解决许多问题。这使得 state 更容易更新,并且有助于确保在嵌套对象的不同部分中没有重复。 @@ -1848,17 +1848,17 @@ button { margin: 10px; } -有时候,你也可以通过将一些嵌套状态移动到子组件中来减少状态的嵌套。这对于不需要保存的短暂 UI 状态非常有效,比如一个选项是否被悬停。 +有时候,你也可以通过将一些嵌套 state 移动到子组件中来减少 state 的嵌套。这对于不需要保存的短暂 UI 状态非常有效,比如一个选项是否被悬停。 * 如果两个 state 变量总是一起更新,请考虑将它们合并为一个。 -* 仔细选择你的 state 变量,以避免创建“极难处理”的状态。 -* 用一种减少出错更新的机会的方式来构建你的状态。 -* 避免冗余和重复的状态,这样您就不需要保持同步。 +* 仔细选择你的 state 变量,以避免创建“极难处理”的 state。 +* 用一种减少出错更新的机会的方式来构建你的 state。 +* 避免冗余和重复的 state,这样您就不需要保持同步。 * 除非您特别想防止更新,否则不要将 props *放入* state中。 * 对于选择类型的 UI 模式,请在 state 中保存 ID 或索引而不是对象本身。 -* 如果深度嵌套状态更新很复杂,请尝试将其展开扁平化。 +* 如果深度嵌套 state 更新很复杂,请尝试将其展开扁平化。 @@ -1921,7 +1921,7 @@ export default function App() { -这个问题点在于此组件的 `color` 状态是使用`color` prop 的初始值进行初始化的。但是当 `color` prop 值发生更改时,这不会影响 state 变量!因此它们会失去同步。为了解决这个问题,完全删除 state 变量,并直接使用 `color` prop 即可。 +这个问题点在于此组件的 `color` state 是使用`color` prop 的初始值进行初始化的。但是当 `color` prop 值发生更改时,这不会影响 state 变量!因此它们会失去同步。为了解决这个问题,完全删除 state 变量,并直接使用 `color` prop 即可。 @@ -2174,7 +2174,7 @@ ul, li { margin: 0; padding: 0; } -虽然你可以仔细更改每个事件处理程序来正确更新 `total` 和 `packed` 计数器,但根本问题在于这些 state 变量一直存在。它们是冗余的,因为你始终可以从 `item` 数组本身计算出物品(已打包或总共)的数量。因此需要删除冗余状态以修复错误: +虽然你可以仔细更改每个事件处理程序来正确更新 `total` 和 `packed` 计数器,但根本问题在于这些 state 变量一直存在。它们是冗余的,因为你始终可以从 `item` 数组本身计算出物品(已打包或总共)的数量。因此需要删除冗余 state 以修复错误: @@ -2313,7 +2313,7 @@ ul, li { margin: 0; padding: 0; } #### 修复消失的选项 {/*fix-the-disappearing-selection*/} -有一个 `letters` 列表在状态中。当你悬停或聚焦到特定的字母时,它会被突出显示。当前突出显示的字母存储在 `highlightedLetter` state 变量中。您可以 “Star” 和 “Unstar” 单个字母,这将更新 state 中的 `letters` 数组。 +有一个 `letters` 列表在 state 中。当你悬停或聚焦到特定的字母时,它会被突出显示。当前突出显示的字母存储在 `highlightedLetter` state 变量中。您可以 “Star” 和 “Unstar” 单个字母,这将更新 state 中的 `letters` 数组。 虽然这段代码可以运行,但是有一个小的 UI 问题。当你点击 “Star” 或 “Unstar” 时,高亮会短暂消失。不过只要你移动鼠标指针或者用键盘切换到另一个字母,它就会重新出现。为什么会这样?请修复它,使得在按钮点击后高亮不会消失。 @@ -2422,7 +2422,7 @@ li { border-radius: 5px; } -这个问题点在于你将字母对象存储在 `highlightedLetter` 中。但是,你也将相同的信息存储在 `letters` 数组中。因此,你的状态存在重复!当你在按钮点击后更新 `letters` 数组时,会创建一个新的字母对象,它与 `highlightedLetter` 不同。这就是为什么 `highlightedLetter === letter` 执行变为 `false`,并且高亮消失的原因。当指针移动时下一次调用 `setHighlightedLetter` 时它会重新出现。 +这个问题点在于你将字母对象存储在 `highlightedLetter` 中。但是,你也将相同的信息存储在 `letters` 数组中。因此,你的 state 存在重复!当你在按钮点击后更新 `letters` 数组时,会创建一个新的字母对象,它与 `highlightedLetter` 不同。这就是为什么 `highlightedLetter === letter` 执行变为 `false`,并且高亮消失的原因。当指针移动时下一次调用 `setHighlightedLetter` 时它会重新出现。 为了解决这个问题,请从 state 中删除重复项。不要在两个地方存储 `字母对象本身`,而是存储 `highlightedId`。然后,您可以使用 `letter.id === highlightedId` 检查每个带有 `isHighlighted` 属性的字母,即使 `letter` 对象在上次渲染后发生了变化,这也是可行的。 @@ -2533,7 +2533,7 @@ li { border-radius: 5px; } #### 实现多选功能 {/*implement-multiple-selection*/} -在这个例子中,每个 `Letter` 都有一个 `isSelected` prop 和一个 `onToggle` 处理程序来标记它为选定状态。这样做是有效的,但是 state 被存储为 `selectedId`(也可以是 `null` 或 `ID`),因此任何时候只能选择一个 letter。 +在这个例子中,每个 `Letter` 都有一个 `isSelected` prop 和一个 `onToggle` 处理程序来标记它为选定 state。这样做是有效的,但是 state 被存储为 `selectedId`(也可以是 `null` 或 `ID`),因此任何时候只能选择一个 letter。 你需要将 state 结构更改为支持多选功能。(在编写代码之前,请考虑如何构建它。)每个复选框应该独立于其他复选框。单击已选择的项目应取消选择。最后,页脚应显示所选项目的正确数量。 @@ -2640,7 +2640,7 @@ label { width: 100%; padding: 5px; display: inline-block; } -在状态中保留一个 `selectedIds` *数组*,而不是单个的 `selectedId`。例如,如果您选择了第一个和最后一个字母,则它将包含 `[0, 2]`。当没有选定任何内容时,它将为空数组 `[]`: +在 state 中保留一个 `selectedIds` *数组*,而不是单个的 `selectedId`。例如,如果您选择了第一个和最后一个字母,则它将包含 `[0, 2]`。当没有选定任何内容时,它将为空数组 `[]`: @@ -2748,7 +2748,7 @@ label { width: 100%; padding: 5px; display: inline-block; } 使用数组的一个小缺点是,对于每个项目,你都需要调用 `selectedIds.includes(letter.id)` 来检查它是否被选中。如果数组非常大,则这可能会成为性能问题,因为带有 [`includes()`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/includes) 的数组搜索需要线性时间,并且你正在为每个单独的项目执行此搜索。 -要解决这个问题,你可以在状态中使用一个 [Set](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Set) 对象,它提供了快速的 [`has()`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Set/has) 操作: +要解决这个问题,你可以在 state 中使用一个 [Set](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Set) 对象,它提供了快速的 [`has()`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Set/has) 操作: @@ -2853,7 +2853,7 @@ label { width: 100%; padding: 5px; display: inline-block; } 现在每个项目都会进行 `selectedIds.has(letter.id)` 检查,这非常快。 -请记住,你[不应该在状态中改变对象](/learn/updating-objects-in-state),包括 Set 中。这就是为什么 `handleToggle` 函数首先创建 Set 的*副本*,然后更新该副本的原因。 +请记住,你[不应该在 state 中改变对象](/learn/updating-objects-in-state),包括 Set 中。这就是为什么 `handleToggle` 函数首先创建 Set 的*副本*,然后更新该副本的原因。 From 2ba559c9cc852b3b6740e5c4ac489ca258962d03 Mon Sep 17 00:00:00 2001 From: davont Date: Thu, 23 Mar 2023 21:40:37 +0800 Subject: [PATCH 04/30] docs(cn): tupdate sidebar translations --- src/sidebarLearn.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sidebarLearn.json b/src/sidebarLearn.json index 0749ddf077..e317bd67a6 100644 --- a/src/sidebarLearn.json +++ b/src/sidebarLearn.json @@ -130,7 +130,7 @@ "path": "/learn/reacting-to-input-with-state" }, { - "title": "选择状态结构", + "title": "选择 State 结构", "path": "/learn/choosing-the-state-structure" }, { From 386a1c7ade90fe4f99584ccedf71a3aa9adfb7da Mon Sep 17 00:00:00 2001 From: Davont Date: Fri, 24 Mar 2023 12:54:59 +0800 Subject: [PATCH 05/30] Update src/content/learn/choosing-the-state-structure.md Co-authored-by: Xavi Lee --- src/content/learn/choosing-the-state-structure.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/content/learn/choosing-the-state-structure.md b/src/content/learn/choosing-the-state-structure.md index d09dd9d71a..e5787f1c8c 100644 --- a/src/content/learn/choosing-the-state-structure.md +++ b/src/content/learn/choosing-the-state-structure.md @@ -12,7 +12,7 @@ translators: -* 何时使用单个 state 变量和多个 state 变量 +* 使用单个 state 变量还是多个 state 变量 * 组织 state 时应避免的内容 * 如何解决 state 结构中的常见问题 From 802fe74f157ba95994f1a7d8bec7f9d7b1c077ed Mon Sep 17 00:00:00 2001 From: Davont Date: Fri, 24 Mar 2023 12:55:30 +0800 Subject: [PATCH 06/30] Update src/content/learn/choosing-the-state-structure.md Co-authored-by: Xavi Lee --- src/content/learn/choosing-the-state-structure.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/content/learn/choosing-the-state-structure.md b/src/content/learn/choosing-the-state-structure.md index e5787f1c8c..b52c9aa52f 100644 --- a/src/content/learn/choosing-the-state-structure.md +++ b/src/content/learn/choosing-the-state-structure.md @@ -374,7 +374,7 @@ function Message({ messageColor }) { ```js function Message({ initialColor }) { - // 这个 `color` state 变量用于保存 `initialColor` 的 *初始值* 值。 + // 这个 `color` state 变量用于保存 `initialColor` 的 *初始值*。 // 对于 `initialColor` 属性的进一步更改将被忽略。 const [color, setColor] = useState(initialColor); ``` From d1c914947d16b14f61dce83f3be0e6debc65c774 Mon Sep 17 00:00:00 2001 From: Davont Date: Fri, 24 Mar 2023 12:55:57 +0800 Subject: [PATCH 07/30] Update src/content/learn/choosing-the-state-structure.md Co-authored-by: Xavi Lee --- src/content/learn/choosing-the-state-structure.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/content/learn/choosing-the-state-structure.md b/src/content/learn/choosing-the-state-structure.md index b52c9aa52f..bc11d62122 100644 --- a/src/content/learn/choosing-the-state-structure.md +++ b/src/content/learn/choosing-the-state-structure.md @@ -6,7 +6,7 @@ translators: -构建良好的 state 可以让组件变得易于修改和调试,而不是成为一个经常出错的组件。以下是你在构建 state 时应该考虑的一些建议。 +构建良好的 state 可以让组件变得易于修改和调试,而不会经常出错。以下是你在构建 state 时应该考虑的一些建议。 From 160ca43c52b1bc28c15c5f6597479fbe20843704 Mon Sep 17 00:00:00 2001 From: Davont Date: Fri, 24 Mar 2023 12:56:13 +0800 Subject: [PATCH 08/30] Update src/content/learn/choosing-the-state-structure.md Co-authored-by: Xavi Lee --- src/content/learn/choosing-the-state-structure.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/content/learn/choosing-the-state-structure.md b/src/content/learn/choosing-the-state-structure.md index bc11d62122..d2e1992595 100644 --- a/src/content/learn/choosing-the-state-structure.md +++ b/src/content/learn/choosing-the-state-structure.md @@ -430,7 +430,7 @@ button { margin-top: 10px; } 当前,它将所选菜作为对象存储在 `selectedItem` state 变量中。然而,这并不好:**`selectedItem` 的内容与 `items` 列表中的某个项是同一个对象。** 这意味着关于该项本身的信息在两个地方产生了重复。 -为什么这是个问题? 让我们使每个项目都可以编辑: +为什么这是个问题?让我们使每个项目都可以编辑: From 32e06427771d68e5ba2bbaa5fcf84bbb2452f4b9 Mon Sep 17 00:00:00 2001 From: davont Date: Fri, 24 Mar 2023 13:04:58 +0800 Subject: [PATCH 09/30] docs(cn): update sidebar translations --- src/sidebarLearn.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sidebarLearn.json b/src/sidebarLearn.json index e317bd67a6..35b7e8c43b 100644 --- a/src/sidebarLearn.json +++ b/src/sidebarLearn.json @@ -186,7 +186,7 @@ }, { "title": "Removing Effect Dependencies", - "path": "/learn/removing-effect-dependencies" + "path": "/learn/removing-effect-dependencies" }, { "title": "Reusing Logic with Custom Hooks", From 0d0fad68b013bdae57d92fd6dd66dddc37e3a461 Mon Sep 17 00:00:00 2001 From: davont Date: Fri, 24 Mar 2023 13:13:41 +0800 Subject: [PATCH 10/30] docs(cn): update sidebar translations --- src/sidebarLearn.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sidebarLearn.json b/src/sidebarLearn.json index 35b7e8c43b..1714366a7c 100644 --- a/src/sidebarLearn.json +++ b/src/sidebarLearn.json @@ -186,7 +186,7 @@ }, { "title": "Removing Effect Dependencies", - "path": "/learn/removing-effect-dependencies" + "path": "/learn/removing-effect-dependencies" }, { "title": "Reusing Logic with Custom Hooks", From 5c839368ba28898df6e27b2596286a1ea0cb7d67 Mon Sep 17 00:00:00 2001 From: davont Date: Mon, 10 Apr 2023 21:25:08 +0800 Subject: [PATCH 11/30] docs(cn): translate the choosing-the-state-structure page --- .../learn/choosing-the-state-structure.md | 40 +++++++++---------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/src/content/learn/choosing-the-state-structure.md b/src/content/learn/choosing-the-state-structure.md index d2e1992595..69f057b505 100644 --- a/src/content/learn/choosing-the-state-structure.md +++ b/src/content/learn/choosing-the-state-structure.md @@ -22,13 +22,13 @@ translators: 当你编写一个存有 state 的组件时,你需要选择使用多少个 state 变量以及它们都是怎样的数据格式。尽管选择次优的 state 结构下也可以编写正确的程序,但有几个原则可以指导您做出更好的决策: -1. **合并关联的 state。** 如果你总是同时更新两个或更多的 state 变量,请考虑将它们合并为一个单独的 state 变量。 -2. **避免互相矛盾的 state。** 当 state 结构中存在多个相互矛盾或“不一致”的 state 时,你就可能为此会留下隐患。应尽量避免这种情况。 -3. **避免冗余的 state。** 如果你能在渲染期间从组件的 props 或其现有的 state 变量中计算出一些信息,则不应将这些信息放入该组件的 state 中。 -4. **避免重复的 state。** 当同一数据在多个 state 变量之间或在多个嵌套对象中重复时,这会很难保持它们同步。应尽可能减少重复。 -5. **避免深度嵌套的 state。** 深度分层的 state 更新起来不是很方便。如果可能的话,最好以扁平化方式构建 state 。 +1. **合并关联的 state**。如果你总是同时更新两个或更多的 state 变量,请考虑将它们合并为一个单独的 state 变量。 +2. **避免互相矛盾的 state**。当 state 结构中存在多个相互矛盾或“不一致”的 state 时,你就可能为此会留下隐患。应尽量避免这种情况。 +3. **避免冗余的 state**。如果你能在渲染期间从组件的 props 或其现有的 state 变量中计算出一些信息,则不应将这些信息放入该组件的 state 中。 +4. **避免重复的 state**。当同一数据在多个 state 变量之间或在多个嵌套对象中重复时,这会很难保持它们同步。应尽可能减少重复。 +5. **避免深度嵌套的 state**。深度分层的 state 更新起来不是很方便。如果可能的话,最好以扁平化方式构建 state。 -这些原则背后的目标是*使 state 易于更新而不引入错误*。从 state 中删除冗余和重复数据有助于确保所有部分保持同步。这类似于数据库工程师想要 [“规范化”数据库结构](https://docs.microsoft.com/en-us/office/troubleshoot/access/database-normalization-description),以减少出现错误的机会。用爱因斯坦的话说,**“让你的状态尽可能简单,但不要过于简单。”** +这些原则背后的目标是 **使 state 易于更新而不引入错误**。从 state 中删除冗余和重复数据有助于确保所有部分保持同步。这类似于数据库工程师想要 [“规范化”数据库结构](https://docs.microsoft.com/en-us/office/troubleshoot/access/database-normalization-description),以减少出现错误的机会。用爱因斯坦的话说,**“让你的状态尽可能简单,但不要过于简单。”** 现在让我们来看看这些原则在实际中是如何应用的。 @@ -162,7 +162,7 @@ function sendMessage(text) { 尽管这段代码是有效的,但也会让一些 state “极难处理”。例如,如果你忘记同时调用 `setIsSent` 和 `setIsSending`,则可能会出现 `isSending` 和 `isSent` 同时为 `true` 的情况。你的组件越复杂,你就越难理解发生了什么。 -**因为 `isSending` 和 `isSent` 不应同时为 `true`,所以最好用一个 `status` 变量来代替它们,这个 state 变量可以采取 *三种* 有效状态其中之一:**`'typing'` (初始), `'sending'`, 和 `'sent'`: +**因为 `isSending` 和 `isSent` 不应同时为 `true`,所以最好用一个 `status` 变量来代替它们,这个 state 变量可以采取 **三种** 有效状态其中之一:**`'typing'` (初始), `'sending'`, 和 `'sent'`: @@ -337,13 +337,13 @@ label { display: block; margin-bottom: 5px; } -这里,`fullName` *不是* 一个 state 变量。相反,它是在渲染期间中计算出的: +这里,`fullName` **不是** 一个 state 变量。相反,它是在渲染期间中计算出的: ```js const fullName = firstName + ' ' + lastName; ``` -因此,更改处理程序不需要做任何特殊操作来更新它。 当你调用 `setFirstName` 或 `setLastName` 时,你会触发一次重新渲染,然后下一个 `fullName` 将从新数据中计算出来。。 +因此,更改处理程序不需要做任何特殊操作来更新它。当你调用 `setFirstName` 或 `setLastName` 时,你会触发一次重新渲染,然后下一个 `fullName` 将从新数据中计算出来。。 @@ -356,7 +356,7 @@ function Message({ messageColor }) { const [color, setColor] = useState(messageColor); ``` -这里,一个 `color` state 变量被初始化为 `messageColor` 的 props 值。 这段代码的问题在于,**如果父组件稍后传递不同的 `messageColor` 值(例如,将其从 `'blue'` 更改为 `'red'`),则`color` *state 变量*将不会更新!** state 仅在第一次渲染期间初始化。 +这里,一个 `color` state 变量被初始化为 `messageColor` 的 props 值。这段代码的问题在于,**如果父组件稍后传递不同的 `messageColor` 值(例如,将其从 `'blue'` 更改为 `'red'`),则`color` **state 变量** 将不会更新!** state 仅在第一次渲染期间初始化。 这就是为什么在 state 变量中,"镜像"一些 prop 属性会导致混淆的原因。相反,你要在代码中直接使用 `messageColor` 属性。如果你想给它起一个更短的名称,请使用常量: @@ -367,10 +367,10 @@ function Message({ messageColor }) { 这种写法就不会与从父组件传递的属性失去同步。 -只有当你 *想要* 忽略特定 props 属性的所有更新时,将 props “镜像”到 state 才有意义。按照惯例,prop 名称以 `initial` 或 `default` 开头,以阐明该 prop 的新值将被忽略: +只有当你 **想要** 忽略特定 props 属性的所有更新时,将 props “镜像”到 state 才有意义。按照惯例,prop 名称以 `initial` 或 `default` 开头,以阐明该 prop 的新值将被忽略: -这个 `color` state 变量用于保存 `initialColor` 的 *初始值* 值。 +这个 `color` state 变量用于保存 `initialColor` 的 **初始值** 值。 ```js function Message({ initialColor }) { @@ -493,9 +493,9 @@ button { margin-top: 10px; } -请注意,如果你首先单击菜单上的“Choose” *然后* 编辑它,**输入会更新,但底部的标签不会反映编辑内容。** 这是因为你有重复的 state,并且你忘记更新了 `selectedItem`。 +请注意,如果你首先单击菜单上的“Choose” **然后** 编辑它,**输入会更新,但底部的标签不会反映编辑内容。** 这是因为你有重复的 state,并且你忘记更新了 `selectedItem`。 -尽管你也可以更新 `selectedItem`,但更简单的解决方法是消除重复项。在下面这个例子中,你将 `selectedId` 保存在 state 中,而不是在 `selectedItem` 对象中(它创建了一个与 `items` 内重复的对象),*然后* 通过搜索 `items` 数组中具有该 ID 的项,以此获取 `selectedItem`: +尽管你也可以更新 `selectedItem`,但更简单的解决方法是消除重复项。在下面这个例子中,你将 `selectedId` 保存在 state 中,而不是在 `selectedItem` 对象中(它创建了一个与 `items` 内重复的对象),**然后** 通过搜索 `items` 数组中具有该 ID 的项,以此获取 `selectedItem`: @@ -575,7 +575,7 @@ state 过去常常是这样复制的: 重复的 state 没有了,你只保留了必要的 state! -现在,如果你编辑 *selected* 项目,下面的消息将立即更新。这是因为 `setItems` 会触发重新渲染,而 `items.find(...)` 会找到带有更新文本的项目。你不需要在 state 中保存 *选定的项目* ,因为只有 *选定的 ID* 是必要的。其余的可以在渲染期间计算。 +现在,如果你编辑 **selected** 项目,下面的消息将立即更新。这是因为 `setItems` 会触发重新渲染,而 `items.find(...)` 会找到带有更新文本的项目。你不需要在 state 中保存 **选定的项目**,因为只有 **选定的 ID** 是必要的。其余的可以在渲染期间计算。 ## 避免深度嵌套的 state {/*avoid-deeply-nested-state*/} @@ -827,7 +827,7 @@ export const initialTravelPlan = { 现在,假设你想添加一个按钮来删除一个你已经去过的地方。你会怎么做呢?[更新嵌套的 state](/learn/updating-objects-in-state#updating-a-nested-object) 需要从更改部分一直向上复制对象。删除一个深度嵌套的地点将涉及复制其整个父级地点链。这样的代码可能非常冗长。 -**如果 state 嵌套太深,难以轻松更新,可以考虑将其“扁平化”。** 这里有一个方法可以重构上面这个数据。不同于树状结构,它每个`节点`都有 *其子节点* 数组,你可以让每个`节点`保存一个 *其子节点ID* 的数组。然后存储一个节点 ID 与相应节点的映射关系。 +**如果 state 嵌套太深,难以轻松更新,可以考虑将其“扁平化”。** 这里有一个方法可以重构上面这个数据。不同于树状结构,它每个`节点`都有 **其子节点** 数组,你可以让每个`节点`保存一个 **其子节点ID** 的数组。然后存储一个节点 ID 与相应节点的映射关系。 这个数据重组可能会让你想起看到一个数据库表: @@ -1143,7 +1143,7 @@ export const initialTravelPlan = { -- 其 *父级* 地点的更新版本应该从其 `childIds` 数组中排除已删除的ID。 +- 其 **父级** 地点的更新版本应该从其 `childIds` 数组中排除已删除的ID。 - 其根级“表”对象的更新版本应包括父级地点的更新版本。 下面是展示如何处理它的一个示例: @@ -1856,7 +1856,7 @@ button { margin: 10px; } * 仔细选择你的 state 变量,以避免创建“极难处理”的 state。 * 用一种减少出错更新的机会的方式来构建你的 state。 * 避免冗余和重复的 state,这样您就不需要保持同步。 -* 除非您特别想防止更新,否则不要将 props *放入* state中。 +* 除非您特别想防止更新,否则不要将 props **放入** state中。 * 对于选择类型的 UI 模式,请在 state 中保存 ID 或索引而不是对象本身。 * 如果深度嵌套 state 更新很复杂,请尝试将其展开扁平化。 @@ -2640,7 +2640,7 @@ label { width: 100%; padding: 5px; display: inline-block; } -在 state 中保留一个 `selectedIds` *数组*,而不是单个的 `selectedId`。例如,如果您选择了第一个和最后一个字母,则它将包含 `[0, 2]`。当没有选定任何内容时,它将为空数组 `[]`: +在 state 中保留一个 `selectedIds` **数组**,而不是单个的 `selectedId`。例如,如果您选择了第一个和最后一个字母,则它将包含 `[0, 2]`。当没有选定任何内容时,它将为空数组 `[]`: @@ -2853,7 +2853,7 @@ label { width: 100%; padding: 5px; display: inline-block; } 现在每个项目都会进行 `selectedIds.has(letter.id)` 检查,这非常快。 -请记住,你[不应该在 state 中改变对象](/learn/updating-objects-in-state),包括 Set 中。这就是为什么 `handleToggle` 函数首先创建 Set 的*副本*,然后更新该副本的原因。 +请记住,你[不应该在 state 中改变对象](/learn/updating-objects-in-state),包括 Set 中。这就是为什么 `handleToggle` 函数首先创建 Set 的 **副本**,然后更新该副本的原因。 From d39197ddab6eaf31940bc4c7a8e93c64e1273c75 Mon Sep 17 00:00:00 2001 From: davont Date: Mon, 10 Apr 2023 21:27:57 +0800 Subject: [PATCH 12/30] docs(cn): translate the choosing-the-state-structure page --- src/content/learn/choosing-the-state-structure.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/content/learn/choosing-the-state-structure.md b/src/content/learn/choosing-the-state-structure.md index 69f057b505..f39679eea8 100644 --- a/src/content/learn/choosing-the-state-structure.md +++ b/src/content/learn/choosing-the-state-structure.md @@ -337,13 +337,13 @@ label { display: block; margin-bottom: 5px; } -这里,`fullName` **不是** 一个 state 变量。相反,它是在渲染期间中计算出的: +这里的 `fullName` **不是** 一个 state 变量。相反,它是在渲染期间中计算出的: ```js const fullName = firstName + ' ' + lastName; ``` -因此,更改处理程序不需要做任何特殊操作来更新它。当你调用 `setFirstName` 或 `setLastName` 时,你会触发一次重新渲染,然后下一个 `fullName` 将从新数据中计算出来。。 +因此,更改处理程序不需要做任何特殊操作来更新它。当你调用 `setFirstName` 或 `setLastName` 时,你会触发一次重新渲染,然后下一个 `fullName` 将从新数据中计算出来。 @@ -358,7 +358,7 @@ function Message({ messageColor }) { 这里,一个 `color` state 变量被初始化为 `messageColor` 的 props 值。这段代码的问题在于,**如果父组件稍后传递不同的 `messageColor` 值(例如,将其从 `'blue'` 更改为 `'red'`),则`color` **state 变量** 将不会更新!** state 仅在第一次渲染期间初始化。 -这就是为什么在 state 变量中,"镜像"一些 prop 属性会导致混淆的原因。相反,你要在代码中直接使用 `messageColor` 属性。如果你想给它起一个更短的名称,请使用常量: +这就是为什么在 state 变量中,“镜像”一些 prop 属性会导致混淆的原因。相反,你要在代码中直接使用 `messageColor` 属性。如果你想给它起一个更短的名称,请使用常量: ```js function Message({ messageColor }) { From e5eed3ec09f8600cb07e48b677ddeab240488925 Mon Sep 17 00:00:00 2001 From: davont Date: Tue, 11 Apr 2023 11:02:46 +0800 Subject: [PATCH 13/30] docs(cn): translate the choosing-the-state-structure page --- src/content/learn/choosing-the-state-structure.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/content/learn/choosing-the-state-structure.md b/src/content/learn/choosing-the-state-structure.md index f39679eea8..458f2fc66f 100644 --- a/src/content/learn/choosing-the-state-structure.md +++ b/src/content/learn/choosing-the-state-structure.md @@ -827,7 +827,7 @@ export const initialTravelPlan = { 现在,假设你想添加一个按钮来删除一个你已经去过的地方。你会怎么做呢?[更新嵌套的 state](/learn/updating-objects-in-state#updating-a-nested-object) 需要从更改部分一直向上复制对象。删除一个深度嵌套的地点将涉及复制其整个父级地点链。这样的代码可能非常冗长。 -**如果 state 嵌套太深,难以轻松更新,可以考虑将其“扁平化”。** 这里有一个方法可以重构上面这个数据。不同于树状结构,它每个`节点`都有 **其子节点** 数组,你可以让每个`节点`保存一个 **其子节点ID** 的数组。然后存储一个节点 ID 与相应节点的映射关系。 +**如果 state 嵌套太深,难以轻松更新,可以考虑将其“扁平化”。** 这里有一个方法可以重构上面这个数据。不同于树状结构,它每个`节点`都有 **其子节点** 数组,你可以让每个`节点`保存一个 **其子节点 ID** 的数组。然后存储一个节点 ID 与相应节点的映射关系。 这个数据重组可能会让你想起看到一个数据库表: @@ -1143,7 +1143,7 @@ export const initialTravelPlan = { -- 其 **父级** 地点的更新版本应该从其 `childIds` 数组中排除已删除的ID。 +- 其 **父级** 地点的更新版本应该从其 `childIds` 数组中排除已删除的 ID。 - 其根级“表”对象的更新版本应包括父级地点的更新版本。 下面是展示如何处理它的一个示例: @@ -1490,7 +1490,7 @@ button { margin: 10px; } #### 改善内存使用 {/*improving-memory-usage*/} -理想情况下,您还应该从“表”对象中删除已删除的项目(以及它们的子项!)以改善内存使用。还可以 [使用Immer](/learn/updating-objects-in-state#write-concise-update-logic-with-immer) 使更新逻辑更加简洁。 +理想情况下,您还应该从“表”对象中删除已删除的项目(以及它们的子项!)以改善内存使用。还可以 [使用 Immer](/learn/updating-objects-in-state#write-concise-update-logic-with-immer) 使更新逻辑更加简洁。 @@ -1921,7 +1921,7 @@ export default function App() { -这个问题点在于此组件的 `color` state 是使用`color` prop 的初始值进行初始化的。但是当 `color` prop 值发生更改时,这不会影响 state 变量!因此它们会失去同步。为了解决这个问题,完全删除 state 变量,并直接使用 `color` prop 即可。 +这个问题点在于此组件的 `color` state 是使用 `color` prop 的初始值进行初始化的。但是当 `color` prop 值发生更改时,这不会影响 state 变量!因此它们会失去同步。为了解决这个问题,完全删除 state 变量,并直接使用 `color` prop 即可。 From d7f0ee5389ecf4844df498fc0950e7f1740d3750 Mon Sep 17 00:00:00 2001 From: davont Date: Tue, 11 Apr 2023 11:32:44 +0800 Subject: [PATCH 14/30] docs(cn): translate the choosing-the-state-structure page --- src/content/learn/choosing-the-state-structure.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/content/learn/choosing-the-state-structure.md b/src/content/learn/choosing-the-state-structure.md index 458f2fc66f..46026522e8 100644 --- a/src/content/learn/choosing-the-state-structure.md +++ b/src/content/learn/choosing-the-state-structure.md @@ -162,7 +162,7 @@ function sendMessage(text) { 尽管这段代码是有效的,但也会让一些 state “极难处理”。例如,如果你忘记同时调用 `setIsSent` 和 `setIsSending`,则可能会出现 `isSending` 和 `isSent` 同时为 `true` 的情况。你的组件越复杂,你就越难理解发生了什么。 -**因为 `isSending` 和 `isSent` 不应同时为 `true`,所以最好用一个 `status` 变量来代替它们,这个 state 变量可以采取 **三种** 有效状态其中之一:**`'typing'` (初始), `'sending'`, 和 `'sent'`: +**因为 `isSending` 和 `isSent` 不应同时为 `true`,所以最好用一个 `status` 变量来代替它们,这个 state 变量可以采取三种有效状态其中之一**:`'typing'` (初始), `'sending'`, 和 `'sent'`: @@ -356,7 +356,7 @@ function Message({ messageColor }) { const [color, setColor] = useState(messageColor); ``` -这里,一个 `color` state 变量被初始化为 `messageColor` 的 props 值。这段代码的问题在于,**如果父组件稍后传递不同的 `messageColor` 值(例如,将其从 `'blue'` 更改为 `'red'`),则`color` **state 变量** 将不会更新!** state 仅在第一次渲染期间初始化。 +这里,一个 `color` state 变量被初始化为 `messageColor` 的 props 值。这段代码的问题在于,**如果父组件稍后传递不同的 `messageColor` 值(例如,将其从 `'blue'` 更改为 `'red'`),则 `color`** state 变量**将不会更新!** state 仅在第一次渲染期间初始化。 这就是为什么在 state 变量中,“镜像”一些 prop 属性会导致混淆的原因。相反,你要在代码中直接使用 `messageColor` 属性。如果你想给它起一个更短的名称,请使用常量: @@ -1856,7 +1856,7 @@ button { margin: 10px; } * 仔细选择你的 state 变量,以避免创建“极难处理”的 state。 * 用一种减少出错更新的机会的方式来构建你的 state。 * 避免冗余和重复的 state,这样您就不需要保持同步。 -* 除非您特别想防止更新,否则不要将 props **放入** state中。 +* 除非您特别想防止更新,否则不要将 props **放入** state 中。 * 对于选择类型的 UI 模式,请在 state 中保存 ID 或索引而不是对象本身。 * 如果深度嵌套 state 更新很复杂,请尝试将其展开扁平化。 From 6034b3e3b8fce43d267403b15353709350904feb Mon Sep 17 00:00:00 2001 From: davont Date: Tue, 11 Apr 2023 11:42:30 +0800 Subject: [PATCH 15/30] docs(cn): translate the choosing-the-state-structure page --- src/content/learn/choosing-the-state-structure.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/content/learn/choosing-the-state-structure.md b/src/content/learn/choosing-the-state-structure.md index 46026522e8..6d6ac442d1 100644 --- a/src/content/learn/choosing-the-state-structure.md +++ b/src/content/learn/choosing-the-state-structure.md @@ -162,7 +162,7 @@ function sendMessage(text) { 尽管这段代码是有效的,但也会让一些 state “极难处理”。例如,如果你忘记同时调用 `setIsSent` 和 `setIsSending`,则可能会出现 `isSending` 和 `isSent` 同时为 `true` 的情况。你的组件越复杂,你就越难理解发生了什么。 -**因为 `isSending` 和 `isSent` 不应同时为 `true`,所以最好用一个 `status` 变量来代替它们,这个 state 变量可以采取三种有效状态其中之一**:`'typing'` (初始), `'sending'`, 和 `'sent'`: +**因为 `isSending` 和 `isSent` 不应同时为 `true`,所以最好用一个 `status` 变量来代替它们,这个 state 变量可以采取 *三种* 有效状态其中之一**:`'typing'` (初始), `'sending'`, 和 `'sent'`: From 12ebbc0de29886a484c5ff4da2fc0a9251b76596 Mon Sep 17 00:00:00 2001 From: Xavi Lee Date: Tue, 11 Apr 2023 19:57:17 +0800 Subject: [PATCH 16/30] Apply suggestions from code review [skip ci] --- src/content/learn/choosing-the-state-structure.md | 1 - 1 file changed, 1 deletion(-) diff --git a/src/content/learn/choosing-the-state-structure.md b/src/content/learn/choosing-the-state-structure.md index 6d6ac442d1..ea40b06e5d 100644 --- a/src/content/learn/choosing-the-state-structure.md +++ b/src/content/learn/choosing-the-state-structure.md @@ -49,7 +49,6 @@ const [y, setY] = useState(0); const [position, setPosition] = useState({ x: 0, y: 0 }); ``` - 从技术上讲,你可以使用其中任何一种方法。但是,**如果某两个 state 变量总是一起变化,则将它们统一成一个 state 变量可能更好**。这样你就不会忘记让它们始终保持同步,就像下面这个例子中,移动光标会同时更新红点的两个坐标: From 0ddff1656a8390dfecfeedfe7b9784dedb50535b Mon Sep 17 00:00:00 2001 From: Davont Date: Wed, 12 Apr 2023 09:33:07 +0800 Subject: [PATCH 17/30] Update src/content/learn/choosing-the-state-structure.md Co-authored-by: Yucohny <79147654+Yucohny@users.noreply.github.com> --- src/content/learn/choosing-the-state-structure.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/content/learn/choosing-the-state-structure.md b/src/content/learn/choosing-the-state-structure.md index ea40b06e5d..65b90df4f7 100644 --- a/src/content/learn/choosing-the-state-structure.md +++ b/src/content/learn/choosing-the-state-structure.md @@ -161,7 +161,7 @@ function sendMessage(text) { 尽管这段代码是有效的,但也会让一些 state “极难处理”。例如,如果你忘记同时调用 `setIsSent` 和 `setIsSending`,则可能会出现 `isSending` 和 `isSent` 同时为 `true` 的情况。你的组件越复杂,你就越难理解发生了什么。 -**因为 `isSending` 和 `isSent` 不应同时为 `true`,所以最好用一个 `status` 变量来代替它们,这个 state 变量可以采取 *三种* 有效状态其中之一**:`'typing'` (初始), `'sending'`, 和 `'sent'`: +**因为 `isSending` 和 `isSent` 不应同时为 `true`,所以最好用一个 `status` 变量来代替它们,这个 state 变量可以采取三种有效状态其中之一**:`'typing'` (初始), `'sending'`, 和 `'sent'`: From 9338b286ec3ee884487d7a5ac1fbed7435e12fdf Mon Sep 17 00:00:00 2001 From: Davont Date: Wed, 12 Apr 2023 09:33:21 +0800 Subject: [PATCH 18/30] Update src/content/learn/choosing-the-state-structure.md Co-authored-by: Yucohny <79147654+Yucohny@users.noreply.github.com> --- src/content/learn/choosing-the-state-structure.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/content/learn/choosing-the-state-structure.md b/src/content/learn/choosing-the-state-structure.md index 65b90df4f7..64299902c5 100644 --- a/src/content/learn/choosing-the-state-structure.md +++ b/src/content/learn/choosing-the-state-structure.md @@ -373,7 +373,7 @@ function Message({ messageColor }) { ```js function Message({ initialColor }) { - // 这个 `color` state 变量用于保存 `initialColor` 的 *初始值*。 + // 这个 `color` state 变量用于保存 `initialColor` 的 **初始值**。 // 对于 `initialColor` 属性的进一步更改将被忽略。 const [color, setColor] = useState(initialColor); ``` From d504bc2a2a666e53f01a10cdc249bd4fd8e0fb30 Mon Sep 17 00:00:00 2001 From: Davont Date: Wed, 12 Apr 2023 09:33:52 +0800 Subject: [PATCH 19/30] Update src/content/learn/choosing-the-state-structure.md Co-authored-by: Yucohny <79147654+Yucohny@users.noreply.github.com> --- src/content/learn/choosing-the-state-structure.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/content/learn/choosing-the-state-structure.md b/src/content/learn/choosing-the-state-structure.md index 64299902c5..262b80b897 100644 --- a/src/content/learn/choosing-the-state-structure.md +++ b/src/content/learn/choosing-the-state-structure.md @@ -427,7 +427,7 @@ button { margin-top: 10px; } -当前,它将所选菜作为对象存储在 `selectedItem` state 变量中。然而,这并不好:**`selectedItem` 的内容与 `items` 列表中的某个项是同一个对象。** 这意味着关于该项本身的信息在两个地方产生了重复。 +当前,它将所选元素作为对象存储在 `selectedItem` state 变量中。然而,这并不好:**`selectedItem` 的内容与 `items` 列表中的某个项是同一个对象。** 这意味着关于该项本身的信息在两个地方产生了重复。 为什么这是个问题?让我们使每个项目都可以编辑: From b1a52970f972ae950a4e4f1839f2a664bdb7ec33 Mon Sep 17 00:00:00 2001 From: Davont Date: Wed, 12 Apr 2023 09:34:19 +0800 Subject: [PATCH 20/30] Update src/content/learn/choosing-the-state-structure.md Co-authored-by: Yucohny <79147654+Yucohny@users.noreply.github.com> --- src/content/learn/choosing-the-state-structure.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/content/learn/choosing-the-state-structure.md b/src/content/learn/choosing-the-state-structure.md index 262b80b897..2b2fe57660 100644 --- a/src/content/learn/choosing-the-state-structure.md +++ b/src/content/learn/choosing-the-state-structure.md @@ -826,7 +826,7 @@ export const initialTravelPlan = { 现在,假设你想添加一个按钮来删除一个你已经去过的地方。你会怎么做呢?[更新嵌套的 state](/learn/updating-objects-in-state#updating-a-nested-object) 需要从更改部分一直向上复制对象。删除一个深度嵌套的地点将涉及复制其整个父级地点链。这样的代码可能非常冗长。 -**如果 state 嵌套太深,难以轻松更新,可以考虑将其“扁平化”。** 这里有一个方法可以重构上面这个数据。不同于树状结构,它每个`节点`都有 **其子节点** 数组,你可以让每个`节点`保存一个 **其子节点 ID** 的数组。然后存储一个节点 ID 与相应节点的映射关系。 +**如果 state 嵌套太深,难以轻松更新,可以考虑将其“扁平化”。** 这里有一个方法可以重构上面这个数据。不同于树状结构,每个节点的 `place` 都是一个包含 **其子节点** 的数组,你可以让每个节点的 `place` 作为数组保存 **其子节点的 ID**。然后存储一个节点 ID 与相应节点的映射关系。 这个数据重组可能会让你想起看到一个数据库表: From d933294d65fae7be469847c9611bffa403528a81 Mon Sep 17 00:00:00 2001 From: Davont Date: Wed, 12 Apr 2023 09:34:30 +0800 Subject: [PATCH 21/30] Update src/content/learn/choosing-the-state-structure.md Co-authored-by: Yucohny <79147654+Yucohny@users.noreply.github.com> --- src/content/learn/choosing-the-state-structure.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/content/learn/choosing-the-state-structure.md b/src/content/learn/choosing-the-state-structure.md index 2b2fe57660..b85ccae800 100644 --- a/src/content/learn/choosing-the-state-structure.md +++ b/src/content/learn/choosing-the-state-structure.md @@ -2312,7 +2312,7 @@ ul, li { margin: 0; padding: 0; } #### 修复消失的选项 {/*fix-the-disappearing-selection*/} -有一个 `letters` 列表在 state 中。当你悬停或聚焦到特定的字母时,它会被突出显示。当前突出显示的字母存储在 `highlightedLetter` state 变量中。您可以 “Star” 和 “Unstar” 单个字母,这将更新 state 中的 `letters` 数组。 +有一个 `letters` 列表在 state 中。当你悬停或聚焦到特定的字母时,它会被突出显示。当前突出显示的字母存储在 `highlightedLetter` state 变量中。您可以“Star”和“Unstar”单个字母,这将更新 state 中的 `letters` 数组。 虽然这段代码可以运行,但是有一个小的 UI 问题。当你点击 “Star” 或 “Unstar” 时,高亮会短暂消失。不过只要你移动鼠标指针或者用键盘切换到另一个字母,它就会重新出现。为什么会这样?请修复它,使得在按钮点击后高亮不会消失。 From e667bad700a4103ebd1d9471859ba4ac26a65a5a Mon Sep 17 00:00:00 2001 From: Davont Date: Wed, 12 Apr 2023 09:34:41 +0800 Subject: [PATCH 22/30] Update src/content/learn/choosing-the-state-structure.md Co-authored-by: Yucohny <79147654+Yucohny@users.noreply.github.com> --- src/content/learn/choosing-the-state-structure.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/content/learn/choosing-the-state-structure.md b/src/content/learn/choosing-the-state-structure.md index b85ccae800..bbd90a2baf 100644 --- a/src/content/learn/choosing-the-state-structure.md +++ b/src/content/learn/choosing-the-state-structure.md @@ -2314,7 +2314,7 @@ ul, li { margin: 0; padding: 0; } 有一个 `letters` 列表在 state 中。当你悬停或聚焦到特定的字母时,它会被突出显示。当前突出显示的字母存储在 `highlightedLetter` state 变量中。您可以“Star”和“Unstar”单个字母,这将更新 state 中的 `letters` 数组。 -虽然这段代码可以运行,但是有一个小的 UI 问题。当你点击 “Star” 或 “Unstar” 时,高亮会短暂消失。不过只要你移动鼠标指针或者用键盘切换到另一个字母,它就会重新出现。为什么会这样?请修复它,使得在按钮点击后高亮不会消失。 +虽然这段代码可以运行,但是有一个小的 UI 问题。当你点击“Star”或“Unstar”时,高亮会短暂消失。不过只要你移动鼠标指针或者用键盘切换到另一个字母,它就会重新出现。为什么会这样?请修复它,使得在按钮点击后高亮不会消失。 From 3c81df2e9eb492e4d42c67d604903b512aff8e8e Mon Sep 17 00:00:00 2001 From: Davont Date: Wed, 12 Apr 2023 09:35:05 +0800 Subject: [PATCH 23/30] Update src/content/learn/choosing-the-state-structure.md Co-authored-by: Yucohny <79147654+Yucohny@users.noreply.github.com> --- src/content/learn/choosing-the-state-structure.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/content/learn/choosing-the-state-structure.md b/src/content/learn/choosing-the-state-structure.md index bbd90a2baf..9f75a3e2a9 100644 --- a/src/content/learn/choosing-the-state-structure.md +++ b/src/content/learn/choosing-the-state-structure.md @@ -2423,7 +2423,7 @@ li { border-radius: 5px; } 这个问题点在于你将字母对象存储在 `highlightedLetter` 中。但是,你也将相同的信息存储在 `letters` 数组中。因此,你的 state 存在重复!当你在按钮点击后更新 `letters` 数组时,会创建一个新的字母对象,它与 `highlightedLetter` 不同。这就是为什么 `highlightedLetter === letter` 执行变为 `false`,并且高亮消失的原因。当指针移动时下一次调用 `setHighlightedLetter` 时它会重新出现。 -为了解决这个问题,请从 state 中删除重复项。不要在两个地方存储 `字母对象本身`,而是存储 `highlightedId`。然后,您可以使用 `letter.id === highlightedId` 检查每个带有 `isHighlighted` 属性的字母,即使 `letter` 对象在上次渲染后发生了变化,这也是可行的。 +为了解决这个问题,请从 state 中删除重复项。不要在两个地方存储 **字母对象本身**,而是存储 `highlightedId`。然后,您可以使用 `letter.id === highlightedId` 检查每个带有 `isHighlighted` 属性的字母,即使 `letter` 对象在上次渲染后发生了变化,这也是可行的。 From 07e9d55bebdbaebe79a197259aaabeb876e6ff3f Mon Sep 17 00:00:00 2001 From: Xavi Lee Date: Sat, 15 Apr 2023 22:27:17 +0800 Subject: [PATCH 24/30] Update src/content/learn/choosing-the-state-structure.md --- src/content/learn/choosing-the-state-structure.md | 1 - 1 file changed, 1 deletion(-) diff --git a/src/content/learn/choosing-the-state-structure.md b/src/content/learn/choosing-the-state-structure.md index 9f75a3e2a9..5d646d71d8 100644 --- a/src/content/learn/choosing-the-state-structure.md +++ b/src/content/learn/choosing-the-state-structure.md @@ -1141,7 +1141,6 @@ export const initialTravelPlan = { - - 其 **父级** 地点的更新版本应该从其 `childIds` 数组中排除已删除的 ID。 - 其根级“表”对象的更新版本应包括父级地点的更新版本。 From 2260d22698cb520edc02b099e1f7c72683c99c7f Mon Sep 17 00:00:00 2001 From: Davont Date: Mon, 17 Apr 2023 14:15:49 +0800 Subject: [PATCH 25/30] Update src/content/learn/choosing-the-state-structure.md Co-authored-by: Xavi Lee --- src/content/learn/choosing-the-state-structure.md | 1 - 1 file changed, 1 deletion(-) diff --git a/src/content/learn/choosing-the-state-structure.md b/src/content/learn/choosing-the-state-structure.md index 5d646d71d8..f6567ecad7 100644 --- a/src/content/learn/choosing-the-state-structure.md +++ b/src/content/learn/choosing-the-state-structure.md @@ -368,7 +368,6 @@ function Message({ messageColor }) { 只有当你 **想要** 忽略特定 props 属性的所有更新时,将 props “镜像”到 state 才有意义。按照惯例,prop 名称以 `initial` 或 `default` 开头,以阐明该 prop 的新值将被忽略: - 这个 `color` state 变量用于保存 `initialColor` 的 **初始值** 值。 ```js From c84e8284ae3e93f62a77406f46fb22ec79cf197f Mon Sep 17 00:00:00 2001 From: davont Date: Tue, 18 Apr 2023 22:23:50 +0800 Subject: [PATCH 26/30] docs(cn): translate the choosing-the-state-structure page --- src/content/learn/choosing-the-state-structure.md | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/content/learn/choosing-the-state-structure.md b/src/content/learn/choosing-the-state-structure.md index f6567ecad7..e54c7c57cf 100644 --- a/src/content/learn/choosing-the-state-structure.md +++ b/src/content/learn/choosing-the-state-structure.md @@ -368,8 +368,6 @@ function Message({ messageColor }) { 只有当你 **想要** 忽略特定 props 属性的所有更新时,将 props “镜像”到 state 才有意义。按照惯例,prop 名称以 `initial` 或 `default` 开头,以阐明该 prop 的新值将被忽略: -这个 `color` state 变量用于保存 `initialColor` 的 **初始值** 值。 - ```js function Message({ initialColor }) { // 这个 `color` state 变量用于保存 `initialColor` 的 **初始值**。 @@ -572,7 +570,6 @@ state 过去常常是这样复制的: 重复的 state 没有了,你只保留了必要的 state! - 现在,如果你编辑 **selected** 项目,下面的消息将立即更新。这是因为 `setItems` 会触发重新渲染,而 `items.find(...)` 会找到带有更新文本的项目。你不需要在 state 中保存 **选定的项目**,因为只有 **选定的 ID** 是必要的。其余的可以在渲染期间计算。 ## 避免深度嵌套的 state {/*avoid-deeply-nested-state*/} @@ -1138,8 +1135,6 @@ export const initialTravelPlan = { 现在要删除一个地点,您只需要更新两个 state 级别: - - - 其 **父级** 地点的更新版本应该从其 `childIds` 数组中排除已删除的 ID。 - 其根级“表”对象的更新版本应包括父级地点的更新版本。 From 69d2a3d467e8b655faef623b070febf73456ac2a Mon Sep 17 00:00:00 2001 From: davont Date: Tue, 18 Apr 2023 22:32:40 +0800 Subject: [PATCH 27/30] docs(cn): translate the choosing-the-state-structure page --- src/content/learn/choosing-the-state-structure.md | 1 - 1 file changed, 1 deletion(-) diff --git a/src/content/learn/choosing-the-state-structure.md b/src/content/learn/choosing-the-state-structure.md index e54c7c57cf..29bd630e90 100644 --- a/src/content/learn/choosing-the-state-structure.md +++ b/src/content/learn/choosing-the-state-structure.md @@ -367,7 +367,6 @@ function Message({ messageColor }) { 这种写法就不会与从父组件传递的属性失去同步。 只有当你 **想要** 忽略特定 props 属性的所有更新时,将 props “镜像”到 state 才有意义。按照惯例,prop 名称以 `initial` 或 `default` 开头,以阐明该 prop 的新值将被忽略: - ```js function Message({ initialColor }) { // 这个 `color` state 变量用于保存 `initialColor` 的 **初始值**。 From 6f48b73a20b5743aae68b7397e31f9e7993ddcf9 Mon Sep 17 00:00:00 2001 From: davont Date: Tue, 18 Apr 2023 22:39:54 +0800 Subject: [PATCH 28/30] docs(cn): translate the choosing-the-state-structure page --- src/content/learn/choosing-the-state-structure.md | 1 + 1 file changed, 1 insertion(+) diff --git a/src/content/learn/choosing-the-state-structure.md b/src/content/learn/choosing-the-state-structure.md index 29bd630e90..e54c7c57cf 100644 --- a/src/content/learn/choosing-the-state-structure.md +++ b/src/content/learn/choosing-the-state-structure.md @@ -367,6 +367,7 @@ function Message({ messageColor }) { 这种写法就不会与从父组件传递的属性失去同步。 只有当你 **想要** 忽略特定 props 属性的所有更新时,将 props “镜像”到 state 才有意义。按照惯例,prop 名称以 `initial` 或 `default` 开头,以阐明该 prop 的新值将被忽略: + ```js function Message({ initialColor }) { // 这个 `color` state 变量用于保存 `initialColor` 的 **初始值**。 From fc8557451d57a37d173e1e7a04caebee7d081251 Mon Sep 17 00:00:00 2001 From: Davont Date: Sat, 22 Apr 2023 12:43:40 +0800 Subject: [PATCH 29/30] Update src/content/learn/choosing-the-state-structure.md Co-authored-by: Xavi Lee --- src/content/learn/choosing-the-state-structure.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/content/learn/choosing-the-state-structure.md b/src/content/learn/choosing-the-state-structure.md index e54c7c57cf..1108f46d05 100644 --- a/src/content/learn/choosing-the-state-structure.md +++ b/src/content/learn/choosing-the-state-structure.md @@ -355,7 +355,7 @@ function Message({ messageColor }) { const [color, setColor] = useState(messageColor); ``` -这里,一个 `color` state 变量被初始化为 `messageColor` 的 props 值。这段代码的问题在于,**如果父组件稍后传递不同的 `messageColor` 值(例如,将其从 `'blue'` 更改为 `'red'`),则 `color`** state 变量**将不会更新!** state 仅在第一次渲染期间初始化。 +这里,一个 `color` state 变量被初始化为 `messageColor` 的 prop 值。这段代码的问题在于,**如果父组件稍后传递不同的 `messageColor` 值(例如,将其从 `'blue'` 更改为 `'red'`),则 `color`** state 变量**将不会更新!** state 仅在第一次渲染期间初始化。 这就是为什么在 state 变量中,“镜像”一些 prop 属性会导致混淆的原因。相反,你要在代码中直接使用 `messageColor` 属性。如果你想给它起一个更短的名称,请使用常量: From 1e9c8beaa74d86282db839b82ba9fb00325fcf80 Mon Sep 17 00:00:00 2001 From: davont Date: Sat, 22 Apr 2023 15:21:31 +0800 Subject: [PATCH 30/30] docs(cn): translate the choosing-the-state-structure page --- src/content/learn/choosing-the-state-structure.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/content/learn/choosing-the-state-structure.md b/src/content/learn/choosing-the-state-structure.md index 1108f46d05..09b4446490 100644 --- a/src/content/learn/choosing-the-state-structure.md +++ b/src/content/learn/choosing-the-state-structure.md @@ -570,7 +570,7 @@ state 过去常常是这样复制的: 重复的 state 没有了,你只保留了必要的 state! -现在,如果你编辑 **selected** 项目,下面的消息将立即更新。这是因为 `setItems` 会触发重新渲染,而 `items.find(...)` 会找到带有更新文本的项目。你不需要在 state 中保存 **选定的项目**,因为只有 **选定的 ID** 是必要的。其余的可以在渲染期间计算。 +现在,如果你编辑 **selected** 元素,下面的消息将立即更新。这是因为 `setItems` 会触发重新渲染,而 `items.find(...)` 会找到带有更新文本的元素。你不需要在 state 中保存 **选定的元素**,因为只有 **选定的 ID** 是必要的。其余的可以在渲染期间计算。 ## 避免深度嵌套的 state {/*avoid-deeply-nested-state*/}