diff --git a/src/content/reference/react/useState.md b/src/content/reference/react/useState.md index d23fa489b3..cfc8ad5812 100644 --- a/src/content/reference/react/useState.md +++ b/src/content/reference/react/useState.md @@ -4,7 +4,7 @@ title: useState -`useState` is a React Hook that lets you add a [state variable](/learn/state-a-components-memory) to your component. +`useState` 是一个 React Hook,它允许你向组件添加一个 [状态变量](/learn/state-a-components-memory)。 ```js const [state, setState] = useState(initialState); @@ -16,11 +16,11 @@ const [state, setState] = useState(initialState); --- -## Reference {/*reference*/} +## 参考 {/*reference*/} ### `useState(initialState)` {/*usestate*/} -Call `useState` at the top level of your component to declare a [state variable.](/learn/state-a-components-memory) +在组件的顶层调用 `useState` 来声明一个 [状态变量](/learn/state-a-components-memory)。 ```js import { useState } from 'react'; @@ -32,32 +32,32 @@ function MyComponent() { // ... ``` -The convention is to name state variables like `[something, setSomething]` using [array destructuring.](https://javascript.info/destructuring-assignment) +按照惯例使用 [数组解构](https://javascript.info/destructuring-assignment) 来命名状态变量,例如 `[something, setSomething]`。 -[See more examples below.](#usage) +[参见下面更多示例](#usage)。 -#### Parameters {/*parameters*/} +#### 参数 {/*parameters*/} -* `initialState`: The value you want the state to be initially. It can be a value of any type, but there is a special behavior for functions. This argument is ignored after the initial render. - * If you pass a function as `initialState`, it will be treated as an _initializer function_. It should be pure, should take no arguments, and should return a value of any type. React will call your initializer function when initializing the component, and store its return value as the initial state. [See an example below.](#avoiding-recreating-the-initial-state) +* `initialState`:你希望 state 初始化的值。它可以是任何类型的值,但对于函数有特殊的行为。在初始渲染后,此参数将被忽略。 + * 如果传递函数作为 `initialState`,则它将被视为 **初始化函数**。它应该是纯函数,不应该接受任何参数,并且应该返回一个任何类型的值。当初始化组件时,React 将调用你的初始化函数,并将其返回值存储为初始状态。[请参见下面的示例](#avoiding-recreating-the-initial-state)。 -#### Returns {/*returns*/} +#### 返回 {/*returns*/} -`useState` returns an array with exactly two values: +`useState` 返回一个由两个值组成的数组: -1. The current state. During the first render, it will match the `initialState` you have passed. -2. The [`set` function](#setstate) that lets you update the state to a different value and trigger a re-render. +1. 当前的 state。在首次渲染时,它将与你传递的 `initialState` 相匹配。 +2. [`set` 函数](#setstate),它可以让你将 state 更新为不同的值并触发重新渲染。 -#### Caveats {/*caveats*/} +#### 注意事项 {/*caveats*/} -* `useState` is a Hook, so you can only call it **at the top level of your component** or your own Hooks. You can't call it inside loops or conditions. If you need that, extract a new component and move the state into it. -* In Strict Mode, React will **call your initializer function twice** in order to [help you find accidental impurities.](#my-initializer-or-updater-function-runs-twice) This is development-only behavior and does not affect production. If your initializer function is pure (as it should be), this should not affect the behavior. The result from one of the calls will be ignored. +* `useState` 是一个 Hook,因此你只能在 **组件的顶层** 或自己的 Hook 中调用它。你不能在循环或条件语句中调用它。如果你需要这样做,请提取一个新组件并将状态移入其中。 +* 在严格模式中,React 将 **两次调用初始化函数**,以 [帮你找到意外的不纯性](#my-initializer-or-updater-function-runs-twice)。这只是开发时的行为,不影响生产。如果你的初始化函数是纯函数(本该是这样),就不应影响该行为。其中一个调用的结果将被忽略。 --- -### `set` functions, like `setSomething(nextState)` {/*setstate*/} +### `set` 函数,例如 `setSomething(nextState)` {/*setstate*/} -The `set` function returned by `useState` lets you update the state to a different value and trigger a re-render. You can pass the next state directly, or a function that calculates it from the previous state: +`useState` 返回的 `set` 函数允许你将 state 更新为不同的值并触发重新渲染。你可以直接传递新状态,也可以传递一个根据先前状态来计算新状态的函数: ```js const [name, setName] = useState('Edward'); @@ -68,34 +68,34 @@ function handleClick() { // ... ``` -#### Parameters {/*setstate-parameters*/} +#### 参数 {/*setstate-parameters*/} -* `nextState`: The value that you want the state to be. It can be a value of any type, but there is a special behavior for functions. - * If you pass a function as `nextState`, it will be treated as an _updater function_. It must be pure, should take the pending state as its only argument, and should return the next state. React will put your updater function in a queue and re-render your component. During the next render, React will calculate the next state by applying all of the queued updaters to the previous state. [See an example below.](#updating-state-based-on-the-previous-state) +* `nextState`:你想要 state 更新为的值。它可以是任何类型的值,但对于函数有特殊的行为。 + * 如果你将函数作为 `nextState` 传递,它将被视为 **更新函数**。它必须是纯函数,只接受待定的 state 作为其唯一参数,并应返回下一个状态。React 将把你的更新函数放入队列中并重新渲染组件。在下一次渲染期间,React 将通过把队列中所有更新函数应用于先前的状态来计算下一个状态。[请参见下面的示例](#updating-state-based-on-the-previous-state)。 -#### Returns {/*setstate-returns*/} +#### 返回值 {/*setstate-returns*/} -`set` functions do not have a return value. +`set` 函数没有返回值。 -#### Caveats {/*setstate-caveats*/} +#### 注意事项 {/*setstate-caveats*/} -* The `set` function **only updates the state variable for the *next* render**. If you read the state variable after calling the `set` function, [you will still get the old value](#ive-updated-the-state-but-logging-gives-me-the-old-value) that was on the screen before your call. +* `set` 函数 **仅更新 *下一次* 渲染的状态变量**。如果在调用 `set` 函数后读取状态变量,则 [仍会得到在调用之前显示在屏幕上的旧值](#ive-updated-the-state-but-logging-gives-me-the-old-value)。 -* If the new value you provide is identical to the current `state`, as determined by an [`Object.is`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is) comparison, React will **skip re-rendering the component and its children.** This is an optimization. Although in some cases React may still need to call your component before skipping the children, it shouldn't affect your code. +* 如果你提供的新值与当前 `state` 相同(由 [`Object.is`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is) 比较确定),React 将 **跳过重新渲染该组件及其子组件**。这是一种优化。虽然在某些情况下 React 仍然需要在跳过子组件之前调用你的组件,但这不应影响你的代码。 -* React [batches state updates.](/learn/queueing-a-series-of-state-updates) It updates the screen **after all the event handlers have run** and have called their `set` functions. This prevents multiple re-renders during a single event. In the rare case that you need to force React to update the screen earlier, for example to access the DOM, you can use [`flushSync`.](/reference/react-dom/flushSync) +* React 会 [批量处理状态更新](/learn/queueing-a-series-of-state-updates)。它会在所有 **事件处理函数运行** 并调用其 `set` 函数后更新屏幕。这可以防止在单个事件期间多次重新渲染。在某些罕见情况下,你需要强制 React 更早地更新屏幕,例如访问 DOM,你可以使用 [`flushSync`](/reference/react-dom/flushSync)。 -* Calling the `set` function *during rendering* is only allowed from within the currently rendering component. React will discard its output and immediately attempt to render it again with the new state. This pattern is rarely needed, but you can use it to **store information from the previous renders**. [See an example below.](#storing-information-from-previous-renders) +* 在渲染期间,只允许在当前渲染组件内部调用 `set` 函数。React 将丢弃其输出并立即尝试使用新状态重新渲染。这种方式很少需要,但你可以使用它来存储 **先前渲染中的信息**。[请参见下面的示例](#storing-information-from-previous-renders)。 -* In Strict Mode, React will **call your updater function twice** in order to [help you find accidental impurities.](#my-initializer-or-updater-function-runs-twice) This is development-only behavior and does not affect production. If your updater function is pure (as it should be), this should not affect the behavior. The result from one of the calls will be ignored. +* 在严格模式中,React 将 **两次调用你的更新函数**,以帮助你找到 [意外的不纯性](#my-initializer-or-updater-function-runs-twice)。这只是开发时的行为,不影响生产。如果你的更新函数是纯函数(本该是这样),就不应影响该行为。其中一次调用的结果将被忽略。 --- -## Usage {/*usage*/} +## 用法 {/*usage*/} -### Adding state to a component {/*adding-state-to-a-component*/} +### 为组件添加状态 {/*adding-state-to-a-component*/} -Call `useState` at the top level of your component to declare one or more [state variables.](/learn/state-a-components-memory) +在组件的顶层调用 `useState` 来声明一个或多个 [状态变量](/learn/state-a-components-memory)。 ```js [[1, 4, "age"], [2, 4, "setAge"], [3, 4, "42"], [1, 5, "name"], [2, 5, "setName"], [3, 5, "'Taylor'"]] import { useState } from 'react'; @@ -106,14 +106,14 @@ function MyComponent() { // ... ``` -The convention is to name state variables like `[something, setSomething]` using [array destructuring.](https://javascript.info/destructuring-assignment) +按照惯例使用 [数组解构](https://javascript.info/destructuring-assignment) 来命名状态变量,例如 `[something, setSomething]`。 -`useState` returns an array with exactly two items: +`useState` 返回一个只包含两个项的数组: -1. The current state of this state variable, initially set to the initial state you provided. -2. The `set` function that lets you change it to any other value in response to interaction. +1. 该状态变量 当前的 state,最初设置为你提供的 初始化 state。 +2. `set` 函数,它允许你在响应交互时将 state 更改为任何其他值。 -To update what’s on the screen, call the `set` function with some next state: +要更新屏幕上的内容,请使用新状态调用 `set` 函数: ```js [[2, 2, "setName"]] function handleClick() { @@ -121,11 +121,11 @@ function handleClick() { } ``` -React will store the next state, render your component again with the new values, and update the UI. +React 会存储新状态,使用新值重新渲染组件,并更新 UI。 -Calling the `set` function [**does not** change the current state in the already executing code](#ive-updated-the-state-but-logging-gives-me-the-old-value): +调用 `set` 函数 [**不会** 改变已经执行的代码中当前的 state](#ive-updated-the-state-but-logging-gives-me-the-old-value): ```js {3} function handleClick() { @@ -134,15 +134,15 @@ function handleClick() { } ``` -It only affects what `useState` will return starting from the *next* render. +它只影响 **下一次** 渲染中 `useState` 返回的内容。 - + -#### Counter (number) {/*counter-number*/} +#### 计数器(数字) {/*counter-number*/} -In this example, the `count` state variable holds a number. Clicking the button increments it. +在这个例子中,`count` 状态变量保存一个数字。点击按钮会将其加一。 @@ -168,9 +168,9 @@ export default function Counter() { -#### Text field (string) {/*text-field-string*/} +#### 文本字段(字符串) {/*text-field-string*/} -In this example, the `text` state variable holds a string. When you type, `handleChange` reads the latest input value from the browser input DOM element, and calls `setText` to update the state. This allows you to display the current `text` below. +在这个例子中,`text` 状态变量保存一个字符串。当你输入时,`handleChange` 从浏览器 input DOM 元素中读取最新的输入值,并调用 `setText` 来更新状态。这允许你在下面展示当前的 `text`。 @@ -200,9 +200,9 @@ export default function MyInput() { -#### Checkbox (boolean) {/*checkbox-boolean*/} +#### 复选框(布尔值) {/*checkbox-boolean*/} -In this example, the `liked` state variable holds a boolean. When you click the input, `setLiked` updates the `liked` state variable with whether the browser checkbox input is checked. The `liked` variable is used to render the text below the checkbox. +在这个例子中,`liked` 状态变量保存一个布尔值。当你点击 input 时,`setLiked` 会根据浏览器复选框是否选中来更新 `liked` 状态变量。`liked` 变量用于渲染复选框下面的文本。 @@ -236,9 +236,9 @@ export default function MyCheckbox() { -#### Form (two variables) {/*form-two-variables*/} +#### 表单(两个变量) {/*form-two-variables*/} -You can declare more than one state variable in the same component. Each state variable is completely independent. +你可以在同一个组件中声明多个状态变量。每个状态变量都是完全独立的。 @@ -276,9 +276,9 @@ button { display: block; margin-top: 10px; } --- -### Updating state based on the previous state {/*updating-state-based-on-the-previous-state*/} +### 根据先前的 state 更新 state {/*updating-state-based-on-the-previous-state*/} -Suppose the `age` is `42`. This handler calls `setAge(age + 1)` three times: +假设 `age` 为 `42`,这个处理函数三次调用 `setAge(age + 1)`: ```js function handleClick() { @@ -288,9 +288,9 @@ function handleClick() { } ``` -However, after one click, `age` will only be `43` rather than `45`! This is because calling the `set` function [does not update](/learn/state-as-a-snapshot) the `age` state variable in the already running code. So each `setAge(age + 1)` call becomes `setAge(43)`. +然而,点击一次后,`age` 将只会变为 `43` 而不是 `45`!这是因为调用 `set` 函数 [不会更新](/learn/state-as-a-snapshot) 已经运行代码中的 `age` 状态变量。因此,每个 `setAge(age + 1)` 调用变成了 `setAge(43)`。 -To solve this problem, **you may pass an *updater function*** to `setAge` instead of the next state: +为了解决这个问题,**你可以向 `setAge` 传递一个 *更新函数***,而不是下一个状态: ```js [[1, 2, "a", 0], [2, 2, "a + 1"], [1, 3, "a", 0], [2, 3, "a + 1"], [1, 4, "a", 0], [2, 4, "a + 1"]] function handleClick() { @@ -300,39 +300,39 @@ function handleClick() { } ``` -Here, `a => a + 1` is your updater function. It takes the pending state and calculates the next state from it. +这里,`a => a + 1` 是更新函数。它获取 待定状态 并从中计算 下一个状态。 -React puts your updater functions in a [queue.](/learn/queueing-a-series-of-state-updates) Then, during the next render, it will call them in the same order: +React 将更新函数放入 [队列](/learn/queueing-a-series-of-state-updates) 中。然后,在下一次渲染期间,它将按照相同的顺序调用它们: -1. `a => a + 1` will receive `42` as the pending state and return `43` as the next state. -1. `a => a + 1` will receive `43` as the pending state and return `44` as the next state. -1. `a => a + 1` will receive `44` as the pending state and return `45` as the next state. +1. `a => a + 1` 将接收 `42` 作为待定状态,并返回 `43` 作为下一个状态。 +1. `a => a + 1` 将接收 `43` 作为待定状态,并返回 `44` 作为下一个状态。 +1. `a => a + 1` 将接收 `44` 作为待定状态,并返回 `45` 作为下一个状态。 -There are no other queued updates, so React will store `45` as the current state in the end. +现在没有其他排队的更新,因此 React 最终将存储 `45` 作为当前状态。 -By convention, it's common to name the pending state argument for the first letter of the state variable name, like `a` for `age`. However, you may also call it like `prevAge` or something else that you find clearer. +按照惯例,通常将待定状态参数命名为状态变量名称的第一个字母,如 `age` 为 `a`。然而,你也可以把它命名为 `prevAge` 或者其他你觉得更清楚的名称。 -React may [call your updaters twice](#my-initializer-or-updater-function-runs-twice) in development to verify that they are [pure.](/learn/keeping-components-pure) +React 在开发环境中可能会 [两次调用你的更新函数](#my-initializer-or-updater-function-runs-twice) 来验证其是否为 [纯函数](/learn/keeping-components-pure)。 -#### Is using an updater always preferred? {/*is-using-an-updater-always-preferred*/} +#### 是否总是优先使用更新函数? {/*is-using-an-updater-always-preferred*/} -You might hear a recommendation to always write code like `setAge(a => a + 1)` if the state you're setting is calculated from the previous state. There is no harm in it, but it is also not always necessary. +你可能会听到这样的建议,如果要设置的状态是根据先前的状态计算得出的,则应始终编写类似于 `setAge(a => a + 1)` 的代码。这样做没有害处,但也不总是必要的。 -In most cases, there is no difference between these two approaches. React always makes sure that for intentional user actions, like clicks, the `age` state variable would be updated before the next click. This means there is no risk of a click handler seeing a "stale" `age` at the beginning of the event handler. +在大多数情况下,这两种方法没有区别。React 始终确保对于有意的用户操作,如单击,`age` 状态变量将在下一次单击之前被更新。这意味着单击事件处理函数在事件处理开始没有得到“过时” `age` 的风险。 -However, if you do multiple updates within the same event, updaters can be helpful. They're also helpful if accessing the state variable itself is inconvenient (you might run into this when optimizing re-renders). +但是,如果在同一事件中进行多个更新,则更新函数可能会有帮助。如果访问状态变量本身不方便(在优化重新渲染时可能会遇到这种情况),它们也很有用。 -If you prefer consistency over slightly more verbose syntax, it's reasonable to always write an updater if the state you're setting is calculated from the previous state. If it's calculated from the previous state of some *other* state variable, you might want to combine them into one object and [use a reducer.](/learn/extracting-state-logic-into-a-reducer) +如果比起轻微的冗余你更喜欢语法的一致性,你正设置的状态又是根据先前的状态计算出来的,那么总是编写一个更新函数是合理的。如果它是从某个其他状态变量的先前状态计算出的,则你可能希望将它们结合成一个对象然后 [使用 reducer](/learn/extracting-state-logic-into-a-reducer)。 - + -#### Passing the updater function {/*passing-the-updater-function*/} +#### 传递更新函数 {/*passing-the-updater-function*/} -This example passes the updater function, so the "+3" button works. +这个例子传递了更新函数,因此“+3”按钮可以正常工作。 @@ -371,9 +371,9 @@ h1 { display: block; margin: 10px; } -#### Passing the next state directly {/*passing-the-next-state-directly*/} +#### 直接传递下一个状态 {/*passing-the-next-state-directly*/} -This example **does not** pass the updater function, so the "+3" button **doesn't work as intended**. +这个示例 **没有** 传递更新函数,所以“+3”按钮 **不能按预期的方式工作**。 @@ -416,32 +416,32 @@ h1 { display: block; margin: 10px; } --- -### Updating objects and arrays in state {/*updating-objects-and-arrays-in-state*/} +### 更新状态中的对象和数组 {/*updating-objects-and-arrays-in-state*/} -You can put objects and arrays into state. In React, state is considered read-only, so **you should *replace* it rather than *mutate* your existing objects**. For example, if you have a `form` object in state, don't mutate it: +你可以将对象和数组放入状态中。在 React 中,状态被认为是只读的,因此 **你应该替换它而不是改变现有对象**。例如,如果你在状态中保存了一个 `form` 对象,请不要改变它: ```js -// 🚩 Don't mutate an object in state like this: +// 🚩 不要像下面这样改变一个对象: form.firstName = 'Taylor'; ``` -Instead, replace the whole object by creating a new one: +相反,可以通过创建一个新对象来替换整个对象: ```js -// ✅ Replace state with a new object +// ✅ 使用新对象替换 state setForm({ ...form, firstName: 'Taylor' }); ``` -Read [updating objects in state](/learn/updating-objects-in-state) and [updating arrays in state](/learn/updating-arrays-in-state) to learn more. +阅读有关 [更新状态中的对象](/learn/updating-objects-in-state) 和 [更新状态中的数组](/learn/updating-arrays-in-state) 来了解更多。 - + -#### Form (object) {/*form-object*/} +#### 表单(对象){/*form-object*/} -In this example, the `form` state variable holds an object. Each input has a change handler that calls `setForm` with the next state of the entire form. The `{ ...form }` spread syntax ensures that the state object is replaced rather than mutated. +在此示例中,`form` 状态变量保存一个对象。每个输入框都有一个变更处理函数,用整个表单的下一个状态调用 `setForm`。`{ ...form }` 展开语法确保替换状态对象而不是改变它。 @@ -512,9 +512,9 @@ input { margin-left: 5px; } -#### Form (nested object) {/*form-nested-object*/} +#### 表单(嵌套对象){/*form-nested-object*/} -In this example, the state is more nested. When you update nested state, you need to create a copy of the object you're updating, as well as any objects "containing" it on the way upwards. Read [updating a nested object](/learn/updating-objects-in-state#updating-a-nested-object) to learn more. +在此示例中,状态更为嵌套。当你更新嵌套状态时,你需要复制一份正在更新的对象,以及向上“包含”它的所有对象。阅读 [更新嵌套对象](/learn/updating-objects-in-state#updating-a-nested-object) 以了解更多。 @@ -624,9 +624,9 @@ img { width: 200px; height: 200px; } -#### List (array) {/*list-array*/} +#### 列表(数组) {/*list-array*/} -In this example, the `todos` state variable holds an array. Each button handler calls `setTodos` with the next version of that array. The `[...todos]` spread syntax, `todos.map()` and `todos.filter()` ensure the state array is replaced rather than mutated. +在本例中,`todos` 状态变量保存一个数组。每个按钮的处理函数使用该数组的下一个版本调用 `setTodos`。`[...todos]` 展开语法,`todos.map()` 和 `todos.filter()` 确保状态数组被替换而不是改变。 @@ -791,9 +791,9 @@ ul, li { margin: 0; padding: 0; } -#### Writing concise update logic with Immer {/*writing-concise-update-logic-with-immer*/} +#### 用 Immer 编写简洁的更新逻辑 {/*writing-concise-update-logic-with-immer*/} -If updating arrays and objects without mutation feels tedious, you can use a library like [Immer](https://github.com/immerjs/use-immer) to reduce repetitive code. Immer lets you write concise code as if you were mutating objects, but under the hood it performs immutable updates: +如果不能直接改变数组和对象来进行更新感觉很烦琐,你可以使用像 [Immer](https://github.com/immerjs/use-immer) 这样的库来减少重复的代码。Immer 可以让你编写简洁的代码,就像你可以直接改变对象一样,但在底层它执行的是不改变的更新: @@ -882,9 +882,9 @@ function ItemList({ artworks, onToggle }) { --- -### Avoiding recreating the initial state {/*avoiding-recreating-the-initial-state*/} +### 避免重复创建初始状态 {/*avoiding-recreating-the-initial-state*/} -React saves the initial state once and ignores it on the next renders. +React 只在初次渲染时保存初始状态,后续渲染时将其忽略。 ```js function TodoList() { @@ -892,9 +892,9 @@ function TodoList() { // ... ``` -Although the result of `createInitialTodos()` is only used for the initial render, you're still calling this function on every render. This can be wasteful if it's creating large arrays or performing expensive calculations. +尽管 `createInitialTodos()` 的结果仅用于初始渲染,但你仍然在每次渲染时调用此函数。如果它创建大数组或执行昂贵的计算,这可能会浪费资源。 -To solve this, you may **pass it as an _initializer_ function** to `useState` instead: +为了解决这个问题,你可以将它 **作为初始化函数传递给** `useState`: ```js function TodoList() { @@ -902,15 +902,15 @@ function TodoList() { // ... ``` -Notice that you’re passing `createInitialTodos`, which is the *function itself*, and not `createInitialTodos()`, which is the result of calling it. If you pass a function to `useState`, React will only call it during initialization. +请注意,你传递的是 `createInitialTodos` **函数本身**,而不是 `createInitialTodos()` 调用该函数的结果。如果将函数传递给 `useState`,React 仅在初始化期间调用它。 -React may [call your initializers twice](#my-initializer-or-updater-function-runs-twice) in development to verify that they are [pure.](/learn/keeping-components-pure) +React 在开发模式下可能会调用你的 [初始化函数](#my-initializer-or-updater-function-runs-twice) 两次,以验证它们是否是 [纯函数](/learn/keeping-components-pure)。 - + -#### Passing the initializer function {/*passing-the-initializer-function*/} +#### 传递初始化函数 {/*passing-the-initializer-function*/} -This example passes the initializer function, so the `createInitialTodos` function only runs during initialization. It does not run when component re-renders, such as when you type into the input. +这个例子传递了初始化函数,因此 `createInitialTodos` 函数仅在初始化期间运行。当组件重新渲染,例如你在输入框中键入内容时,它不会再次运行。 @@ -961,9 +961,9 @@ export default function TodoList() { -#### Passing the initial state directly {/*passing-the-initial-state-directly*/} +#### 直接传递初始状态 {/*passing-the-initial-state-directly*/} -This example **does not** pass the initializer function, so the `createInitialTodos` function runs on every render, such as when you type into the input. There is no observable difference in behavior, but this code is less efficient. +这个例子 **没有** 传递初始化函数,因此 `createInitialTodos` 函数会在每次渲染时运行,比如当你在输入框中输入时。这种行为没有什么明显的差异,但这种代码是不那么高效的。 @@ -1018,13 +1018,13 @@ export default function TodoList() { --- -### Resetting state with a key {/*resetting-state-with-a-key*/} +### 使用 key 重置状态 {/*resetting-state-with-a-key*/} -You'll often encounter the `key` attribute when [rendering lists.](/learn/rendering-lists) However, it also serves another purpose. +在 [渲染列表](/learn/rendering-lists) 时,你经常会遇到 `key` 属性。然而,它还有另外一个用途。 -You can **reset a component's state by passing a different `key` to a component.** In this example, the Reset button changes the `version` state variable, which we pass as a `key` to the `Form`. When the `key` changes, React re-creates the `Form` component (and all of its children) from scratch, so its state gets reset. +你可以 **通过向组件传递不同的 `key` 来重置组件的状态**。在这个例子中,重置按钮改变 `version` 状态变量,我们将它作为一个 `key` 传递给 `Form` 组件。当 `key` 改变时,React 会从头开始重新创建 `Form` 组件(以及它的所有子组件),所以它的状态被重置了。 -Read [preserving and resetting state](/learn/preserving-and-resetting-state) to learn more. +阅读 [保留和重置状态](/learn/preserving-and-resetting-state) 以了解更多。 @@ -1069,19 +1069,19 @@ button { display: block; margin-bottom: 20px; } --- -### Storing information from previous renders {/*storing-information-from-previous-renders*/} +### 存储前一次渲染的信息 {/*storing-information-from-previous-renders*/} -Usually, you will update state in event handlers. However, in rare cases you might want to adjust state in response to rendering -- for example, you might want to change a state variable when a prop changes. +通常情况下,你会在事件处理函数中更新状态。然而,在极少数情况下,你可能希望在响应渲染时调整状态——例如,当 props 改变时,你可能希望改变状态变量。 -In most cases, you don't need this: +在大多数情况下,你不需要这样做: -* **If the value you need can be computed entirely from the current props or other state, [remove that redundant state altogether.](/learn/choosing-the-state-structure#avoid-redundant-state)** If you're worried about recomputing too often, the [`useMemo` Hook](/reference/react/useMemo) can help. -* If you want to reset the entire component tree's state, [pass a different `key` to your component.](#resetting-state-with-a-key) -* If you can, update all the relevant state in the event handlers. +* **如果你需要的值可以完全从当前 props 或其他 state 中计算出来,那么 [完全可以移除那些多余的状态](/learn/choosing-the-state-structure#avoid-redundant-state)**。如果你担心重新计算的频率过高,可以使用 [`useMemo` Hook](/reference/react/useMemo) 来帮助优化。 +* 如果你想重置整个组件树的状态,[可以向组件传递一个不同的 `key`](#resetting-state-with-a-key)。 +* 如果可以的话,在事件处理函数中更新所有相关状态。 -In the rare case that none of these apply, there is a pattern you can use to update state based on the values that have been rendered so far, by calling a `set` function while your component is rendering. +在极为罕见的情况下,如果上述方法都不适用,你还可以使用一种方式,在组件渲染时调用 `set` 函数来基于已经渲染的值更新状态。 -Here's an example. This `CountLabel` component displays the `count` prop passed to it: +这里是一个例子。这个 `CountLabel` 组件显示传递给它的 `count` props: ```js CountLabel.js export default function CountLabel({ count }) { @@ -1089,7 +1089,7 @@ export default function CountLabel({ count }) { } ``` -Say you want to show whether the counter has *increased or decreased* since the last change. The `count` prop doesn't tell you this -- you need to keep track of its previous value. Add the `prevCount` state variable to track it. Add another state variable called `trend` to hold whether the count has increased or decreased. Compare `prevCount` with `count`, and if they're not equal, update both `prevCount` and `trend`. Now you can show both the current count prop and *how it has changed since the last render*. +假设你想显示计数器是否自上次更改以来 **增加或减少**。`count` props 无法告诉你这一点——你需要跟踪它的先前值。添加 `prevCount` 状态变量来跟踪它,再添加另一个状态变量 `trend` 来保存计数是否增加或减少。比较 `prevCount` 和 `count`,如果它们不相等,则更新 `prevCount` 和 `trend`。现在你既可以显示当前的 `count` props,也可以显示 **自上次渲染以来它如何改变**。 @@ -1138,34 +1138,34 @@ button { margin-bottom: 10px; } -Note that if you call a `set` function while rendering, it must be inside a condition like `prevCount !== count`, and there must be a call like `setPrevCount(count)` inside of the condition. Otherwise, your component would re-render in a loop until it crashes. Also, you can only update the state of the *currently rendering* component like this. Calling the `set` function of *another* component during rendering is an error. Finally, your `set` call should still [update state without mutation](#updating-objects-and-arrays-in-state) -- this doesn't mean you can break other rules of [pure functions.](/learn/keeping-components-pure) +请注意,在渲染时调用 `set` 函数时,它必须位于条件语句中,例如 `prevCount !== count`,并且必须在该条件语句中调用 `setPrevCount(count)`。否则,你的组件将在循环中重新渲染,直到崩溃。此外,你只能像这样更新 **当前渲染** 组件的状态。在渲染过程中调用 **另一个** 组件的 `set` 函数是错误的。最后,你的 `set` 调用仍应 [不直接改变 state 来更新](#updating-objects-and-arrays-in-state) 状态——这并不意味着你可以违反其他 [纯函数](/learn/keeping-components-pure) 的规则。 -This pattern can be hard to understand and is usually best avoided. However, it's better than updating state in an effect. When you call the `set` function during render, React will re-render that component immediately after your component exits with a `return` statement, and before rendering the children. This way, children don't need to render twice. The rest of your component function will still execute (and the result will be thrown away). If your condition is below all the Hook calls, you may add an early `return;` to restart rendering earlier. +这种方式可能很难理解,通常最好避免使用。但是,它比在 effect 中更新状态要好。当你在渲染期间调用 `set` 函数时,React 将在你的组件使用 `return` 语句退出后立即重新渲染该组件,并在渲染子组件前进行。这样,子组件就不需要进行两次渲染。你的组件函数的其余部分仍会执行(然后结果将被丢弃)。如果你的条件判断在所有 Hook 调用的下方,可以提前添加一个 `return;` 以便更早地重新开始渲染。 --- -## Troubleshooting {/*troubleshooting*/} +## 疑难解答 {/*troubleshooting*/} -### I've updated the state, but logging gives me the old value {/*ive-updated-the-state-but-logging-gives-me-the-old-value*/} +### 我已经更新了状态,但日志仍显示旧值 {/*ive-updated-the-state-but-logging-gives-me-the-old-value*/} -Calling the `set` function **does not change state in the running code**: +调用 `set` 函数 **不能改变运行中代码的状态**: ```js {4,5,8} function handleClick() { console.log(count); // 0 - setCount(count + 1); // Request a re-render with 1 - console.log(count); // Still 0! + setCount(count + 1); // 请求使用 1 重新渲染 + console.log(count); // 仍然是 0! setTimeout(() => { - console.log(count); // Also 0! + console.log(count); // 还是 0! }, 5000); } ``` -This is because [states behaves like a snapshot.](/learn/state-as-a-snapshot) Updating state requests another render with the new state value, but does not affect the `count` JavaScript variable in your already-running event handler. +这是因为 [状态表现为就像一个快照](/learn/state-as-a-snapshot)。更新状态会使用新的状态值请求另一个渲染,但并不影响在你已经运行的事件处理函数中的 `count` JavaScript 变量。 -If you need to use the next state, you can save it in a variable before passing it to the `set` function: +如果你需要使用下一个状态,你可以在将其传递给 `set` 函数之前将其保存在一个变量中: ```js const nextCount = count + 1; @@ -1177,19 +1177,19 @@ console.log(nextCount); // 1 --- -### I've updated the state, but the screen doesn't update {/*ive-updated-the-state-but-the-screen-doesnt-update*/} +### 我已经更新了状态,但是屏幕没有更新 {/*ive-updated-the-state-but-the-screen-doesnt-update*/} -React will **ignore your update if the next state is equal to the previous state,** as determined by an [`Object.is`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is) comparison. This usually happens when you change an object or an array in state directly: +**如果下一个状态等于先前的状态,React 将忽略你的更新**,这是由 [`Object.is`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is) 比较确定的。这通常发生在你直接更改状态中的对象或数组时: ```js -obj.x = 10; // 🚩 Wrong: mutating existing object -setObj(obj); // 🚩 Doesn't do anything +obj.x = 10; // 🚩 错误:直接修改现有的对象 +setObj(obj); // 🚩 不会发生任何事情 ``` -You mutated an existing `obj` object and passed it back to `setObj`, so React ignored the update. To fix this, you need to ensure that you're always [_replacing_ objects and arrays in state instead of _mutating_ them](#updating-objects-and-arrays-in-state): +你修改了一个现有的 `obj` 对象并将其传递回 `setObj`,因此 React 忽略了更新。为了解决这个问题,你需要确保始终[在状态中 **替换** 对象和数组,而不是对它们进行 **更改**](#updating-objects-and-arrays-in-state): ```js -// ✅ Correct: creating a new object +// ✅ 正确:创建一个新对象 setObj({ ...obj, x: 10 @@ -1198,78 +1198,78 @@ setObj({ --- -### I'm getting an error: "Too many re-renders" {/*im-getting-an-error-too-many-re-renders*/} +### 出现错误:“Too many re-renders” {/*im-getting-an-error-too-many-re-renders*/} -You might get an error that says: `Too many re-renders. React limits the number of renders to prevent an infinite loop.` Typically, this means that you're unconditionally setting state *during render*, so your component enters a loop: render, set state (which causes a render), render, set state (which causes a render), and so on. Very often, this is caused by a mistake in specifying an event handler: +有时可能会出现错误:“Too many re-renders”。React 会限制渲染次数,以防止进入无限循环。通常,这意味着 **在渲染期间** 无条件地设置状态,因此组件进入循环:渲染、设置状态(导致重新渲染)、渲染、设置状态(导致重新渲染)等等。通常,这是由错误地指定事件处理函数时引起的: ```js {1-2} -// 🚩 Wrong: calls the handler during render +// 🚩 错误:在渲染过程中调用事件处理函数 return -// ✅ Correct: passes down the event handler +// ✅ 正确:将事件处理函数传递下去 return -// ✅ Correct: passes down an inline function +// ✅ 正确:传递一个内联函数 return ``` -If you can't find the cause of this error, click on the arrow next to the error in the console and look through the JavaScript stack to find the specific `set` function call responsible for the error. +如果找不到这个错误的原因,请单击控制台中错误旁边的箭头,查看 JavaScript 堆栈以找到导致错误的具体 `set` 函数调用。 --- -### My initializer or updater function runs twice {/*my-initializer-or-updater-function-runs-twice*/} +### 初始化函数或更新函数运行了两次 {/*my-initializer-or-updater-function-runs-twice*/} -In [Strict Mode](/reference/react/StrictMode), React will call some of your functions twice instead of once: +在 [严格模式](/reference/react/StrictMode) 下,React 会调用你的某些函数两次而不是一次: ```js {2,5-6,11-12} function TodoList() { - // This component function will run twice for every render. + // 该函数组件会在每次渲染运行两次。 const [todos, setTodos] = useState(() => { - // This initializer function will run twice during initialization. + // 该初始化函数在初始化期间会运行两次。 return createTodos(); }); function handleClick() { setTodos(prevTodos => { - // This updater function will run twice for every click. + // 该更新函数在每次点击中都会运行两次 return [...prevTodos, createTodo()]; }); } // ... ``` -This is expected and shouldn't break your code. +这是所期望的,且不应该破坏你的代码。 -This **development-only** behavior helps you [keep components pure.](/learn/keeping-components-pure) React uses the result of one of the calls, and ignores the result of the other call. As long as your component, initializer, and updater functions are pure, this shouldn't affect your logic. However, if they are accidentally impure, this helps you notice the mistakes. +这种 **仅在开发环境下生效** 的行为有助于 [保持组件的纯粹性](/learn/keeping-components-pure)。React 使用其中一个调用的结果,而忽略另一个调用的结果。只要你的组件、初始化函数和更新函数是纯粹的,就不会影响你的逻辑。但是,如果它们意外地不纯粹,这将帮助你注意到错误。 -For example, this impure updater function mutates an array in state: +例如,这个不纯的更新函数改变了 state 中的一个数组: ```js {2,3} setTodos(prevTodos => { - // 🚩 Mistake: mutating state + // 🚩 错误:改变 state prevTodos.push(createTodo()); }); ``` -Because React calls your updater function twice, you'll see the todo was added twice, so you'll know that there is a mistake. In this example, you can fix the mistake by [replacing the array instead of mutating it](#updating-objects-and-arrays-in-state): +因为 React 调用了两次更新函数,所以你将看到 todo 被添加了两次,所以你将知道出现了错误。在这个例子中,你可以通过 [替换数组而不是更改数组](#updating-objects-and-arrays-in-state) 来修复这个错误: ```js {2,3} setTodos(prevTodos => { - // ✅ Correct: replacing with new state + // ✅ 正确:使用新状态替换 return [...prevTodos, createTodo()]; }); ``` -Now that this updater function is pure, calling it an extra time doesn't make a difference in behavior. This is why React calling it twice helps you find mistakes. **Only component, initializer, and updater functions need to be pure.** Event handlers don't need to be pure, so React will never call your event handlers twice. +现在,这个更新函数是纯粹的,所以多调用一次不会对行为产生影响。这就是为什么 React 调用它两次可以帮助你找到错误的原因。**只有组件、初始化函数和更新函数需要是纯粹的**。事件处理函数不需要是纯粹的,所以 React 不会两次调用你的事件处理函数。 -Read [keeping components pure](/learn/keeping-components-pure) to learn more. +阅读 [保持组件纯粹](/learn/keeping-components-pure) 以了解更多信息。 --- -### I'm trying to set state to a function, but it gets called instead {/*im-trying-to-set-state-to-a-function-but-it-gets-called-instead*/} +### 我尝试将 state 设置为一个函数,但它却被调用了 {/*im-trying-to-set-state-to-a-function-but-it-gets-called-instead*/} -You can't put a function into state like this: +你不能像这样把函数放入状态: ```js const [fn, setFn] = useState(someFunction); @@ -1279,7 +1279,7 @@ function handleClick() { } ``` -Because you're passing a function, React assumes that `someFunction` is an [initializer function](#avoiding-recreating-the-initial-state), and that `someOtherFunction` is an [updater function](#updating-state-based-on-the-previous-state), so it tries to call them and store the result. To actually *store* a function, you have to put `() =>` before them in both cases. Then React will store the functions you pass. +因为你传递了一个函数,React 认为 `someFunction` 是一个 [初始化函数](#avoiding-recreating-the-initial-state),而 `someOtherFunction` 是一个 [更新函数](#updating-state-based-on-the-previous-state),于是它尝试调用它们并存储结果。要实际 **存储** 一个函数,你必须在两种情况下在它们之前加上 `() =>`。然后 React 将存储你传递的函数。 ```js {1,4} const [fn, setFn] = useState(() => someFunction);