useState
是一个 React 钩子,可让你将 状态变量 添加到组件中。
¥useState
is a React Hook that lets you add a state variable to your component.
const [state, setState] = useState(initialState)
参考
¥Reference
useState(initialState)
在组件的顶层调用 useState
以声明 状态变量。
¥Call useState
at the top level of your component to declare a state variable.
import { useState } from 'react';
function MyComponent() {
const [age, setAge] = useState(28);
const [name, setName] = useState('Taylor');
const [todos, setTodos] = useState(() => createTodos());
// ...
约定是使用 数组解构 命名状态变量,例如 [something, setSomething]
。
¥The convention is to name state variables like [something, setSomething]
using array destructuring.
参数
¥Parameters
-
initialState
:你希望状态的初始值。它可以是任何类型的值,但函数有特殊的行为。这个参数在初始渲染后被忽略。¥
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.-
如果你将函数作为
initialState
传递,它将被视为初始化函数。它应该是纯粹的,不带任何参数,并且应该返回任何类型的值。React 在初始化组件时会调用你的初始化函数,并将其返回值存储为初始状态。请参见下面的示例。¥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.
-
返回
¥Returns
useState
返回一个恰好包含两个值的数组:
¥useState
returns an array with exactly two values:
-
当前状态。在第一次渲染期间,它将与你传递的
initialState
相匹配。¥The current state. During the first render, it will match the
initialState
you have passed. -
set
函数 允许你将状态更新为不同的值并触发重新渲染。¥The
set
function that lets you update the state to a different value and trigger a re-render.
注意事项
¥Caveats
-
useState
是一个 Hook,所以你只能在你的组件的顶层或者你自己的钩子中调用它。你不能在循环或条件内调用它。如果需要,提取一个新组件并将状态移入其中。¥
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. -
在严格模式下,React 将调用你的初始化函数两次,以便 帮助你发现意外杂质 这是仅开发行为,不会影响生产。如果你的初始化函数是纯函数(它应该是纯函数),这应该不会影响行为。其中一个调用的结果将被忽略。
¥In Strict Mode, React will call your initializer function twice in order to help you find accidental impurities. 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.
set
函数与 setSomething(nextState)
类似
¥set
functions, like setSomething(nextState)
useState
返回的 set
函数允许你将状态更新为不同的值并触发重新渲染。你可以直接传递下一个状态,或从前一个状态计算它的函数:
¥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:
const [name, setName] = useState('Edward');
function handleClick() {
setName('Taylor');
setAge(a => a + 1);
// ...
参数
¥Parameters
-
nextState
:你希望状态成为的值。它可以是任何类型的值,但函数有特殊的行为。¥
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.-
如果你将函数作为
nextState
传递,它将被视为更新函数。它必须是纯粹的,应该将挂起状态作为其唯一参数,并且应该返回下一个状态。React 会将你的更新程序函数放入队列中并重新渲染你的组件。在下一次渲染期间,React 将通过将所有排队的更新器应用于前一个状态来计算下一个状态。请参见下面的示例。¥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.
-
返回
¥Returns
set
函数没有返回值。
¥set
functions do not have a return value.
注意事项
¥Caveats
-
set
函数仅更新下一次渲染的状态变量。如果你在调用set
函数后读取状态变量,则 你仍然会得到旧的值 在你调用之前显示在屏幕上。¥The
set
function only updates the state variable for the next render. If you read the state variable after calling theset
function, you will still get the old value that was on the screen before your call. -
如果你提供的新值与当前的
state
相同(通过Object.is
比较确定),React 将跳过重新渲染组件及其子组件。这是一个优化。尽管在某些情况下 React 可能仍需要在跳过子级之前调用你的组件,但这不应该影响你的代码。¥If the new value you provide is identical to the current
state
, as determined by anObject.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. -
React 批量状态更新。 在所有事件处理程序运行并调用其
set
函数后更新屏幕。这可以防止在单个事件期间多次重新渲染。在极少数情况下,你需要强制 React 提前更新屏幕,例如访问 DOM,你可以使用flushSync
。¥React batches 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 useflushSync
. -
set
函数具有稳定的标识,因此你经常会看到它从效果依赖中省略,但包含它不会导致效果触发。如果 linter 允许你在没有错误的情况下省略依赖,那么这样做是安全的。详细了解如何删除副作用依赖。¥The
set
function has a stable identity, so you will often see it omitted from effect dependencies, but including it will not cause the effect to fire. If the linter lets you omit a dependency without errors, it is safe to do. Learn more about removing Effect dependencies. -
在渲染期间调用
set
函数只能从当前渲染组件中调用。React 将丢弃其输出并立即尝试使用新状态再次渲染它。这种模式很少需要,但你可以使用它来存储先前渲染的信息。请参见下面的示例。¥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. -
在严格模式下,React 将调用你的更新程序函数两次,以便 帮助你发现意外杂质 这是仅开发行为,不会影响生产。如果你的更新程序函数是纯函数(它应该是纯函数),这应该不会影响行为。其中一个调用的结果将被忽略。
¥In Strict Mode, React will call your updater function twice in order to help you find accidental impurities. 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.
用法
¥Usage
向组件添加状态
¥Adding state to a component
在组件的顶层调用 useState
以声明一个或多个 状态变量。
¥Call useState
at the top level of your component to declare one or more state variables.
import { useState } from 'react';
function MyComponent() {
const [age, setAge] = useState(42);
const [name, setName] = useState('Taylor');
// ...
约定是使用 数组解构 命名状态变量,例如 [something, setSomething]
。
¥The convention is to name state variables like [something, setSomething]
using array destructuring.
useState
返回一个恰好包含两项的数组:
¥useState
returns an array with exactly two items:
-
此状态变量的当前状态,初始设置为你提供的初始状态。
¥The current state of this state variable, initially set to the initial state you provided.
-
set
函数 允许你将其更改为任何其他值以响应交互。¥The
set
function that lets you change it to any other value in response to interaction.
要更新屏幕上的内容,请使用下一个状态调用 set
函数:
¥To update what’s on the screen, call the set
function with some next state:
function handleClick() {
setName('Robin');
}
React 将存储下一个状态,使用新值再次渲染你的组件,并更新 UI。
¥React will store the next state, render your component again with the new values, and update the UI.
例子 1 / 4: 计数器(数字)
¥Counter (number)
在此示例中,count
状态变量包含一个数字。单击该按钮会增加它。
¥In this example, the count
state variable holds a number. Clicking the button increments it.
import { useState } from 'react'; export default function Counter() { const [count, setCount] = useState(0); function handleClick() { setCount(count + 1); } return ( <button onClick={handleClick}> You pressed me {count} times </button> ); }
根据之前的状态更新状态
¥Updating state based on the previous state
假设 age
是 42
。此处理程序调用 setAge(age + 1)
三次:
¥Suppose the age
is 42
. This handler calls setAge(age + 1)
three times:
function handleClick() {
setAge(age + 1); // setAge(42 + 1)
setAge(age + 1); // setAge(42 + 1)
setAge(age + 1); // setAge(42 + 1)
}
但是,点击之后,age
只会变成 43
,而不是 45
!这是因为在已经运行的代码中调用 set
函数 不会更新 age
状态变量。所以每次 setAge(age + 1)
调用都变成 setAge(43)
。
¥However, after one click, age
will only be 43
rather than 45
! This is because calling the set
function does not update the age
state variable in the already running code. So each setAge(age + 1)
call becomes setAge(43)
.
为了解决这个问题,你可以将更新函数传递给 setAge
而不是下一个状态:
¥To solve this problem, you may pass an updater function to setAge
instead of the next state:
function handleClick() {
setAge(a => a + 1); // setAge(42 => 43)
setAge(a => a + 1); // setAge(43 => 44)
setAge(a => a + 1); // setAge(44 => 45)
}
这里,a => a + 1
是你的更新函数。它采用 挂起状态 并从中计算出 下一个状态。
¥Here, a => a + 1
is your updater function. It takes the pending state and calculates the next state from it.
React 将你的更新函数放到 队列 中。然后,在下一次渲染期间,它将以相同的顺序调用它们:
¥React puts your updater functions in a queue. Then, during the next render, it will call them in the same order:
-
a => a + 1
将接收42
作为挂起状态,并返回43
作为下一个状态。¥
a => a + 1
will receive42
as the pending state and return43
as the next state. -
a => a + 1
将接收43
作为挂起状态,并返回44
作为下一个状态。¥
a => a + 1
will receive43
as the pending state and return44
as the next state. -
a => a + 1
将接收44
作为挂起状态,并返回45
作为下一个状态。¥
a => a + 1
will receive44
as the pending state and return45
as the next state.
没有其他排队的更新,所以 React 最后会将 45
存储为当前状态。
¥There are no other queued updates, so React will store 45
as the current state in the end.
按照惯例,通常将状态变量名称的第一个字母命名为挂起状态参数,例如 a
代表 age
。但是,你也可以将其称为 prevAge
或你认为更清楚的其他名称。
¥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.
React 可能在开发中 两次调用你的更新器 以验证它们是 纯粹的。
¥React may call your updaters twice in development to verify that they are pure.
深入研究
¥Is using an updater always preferred?
如果你设置的状态是根据先前状态计算得出的,你可能会听到建议始终编写 setAge(a => a + 1)
之类的代码。它没有害处,但也不总是必要的。
¥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.
在大多数情况下,这两种方法之间没有区别。React 始终确保对于有意的用户操作(如点击),age
状态变量将在下一次点击之前更新。这意味着点击处理程序不会在事件处理程序的开头看到 “失效的” age
。
¥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.
但是,如果你在同一事件中进行多次更新,更新器可能会有所帮助。如果访问状态变量本身不方便,它们也会很有帮助(优化重新渲染时可能会遇到这种情况)。
¥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).
如果你更喜欢一致性而不是稍微冗长的语法,那么如果你设置的状态是根据先前状态计算得出的,那么始终编写更新器是合理的。如果它是根据某个其他状态变量的先前状态计算的,你可能希望将它们组合成一个对象和 使用 reducer。
¥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.
例子 1 / 2: 传递更新函数
¥Passing the updater function
此示例传递了更新函数,因此 “+3” 按钮起作用。
¥This example passes the updater function, so the “+3” button works.
import { useState } from 'react'; export default function Counter() { const [age, setAge] = useState(42); function increment() { setAge(a => a + 1); } return ( <> <h1>Your age: {age}</h1> <button onClick={() => { increment(); increment(); increment(); }}>+3</button> <button onClick={() => { increment(); }}>+1</button> </> ); }
更新状态中的对象和数组
¥Updating objects and arrays in state
你可以将对象和数组放入状态。在 React 中,状态被认为是只读的,因此你应该替换它而不是改变现有的对象。例如,如果你有一个状态为 form
的对象,不要改变它:
¥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:
// 🚩 Don't mutate an object in state like this:
form.firstName = 'Taylor';
而是,通过创建一个新对象来替换整个对象:
¥Instead, replace the whole object by creating a new one:
// ✅ Replace state with a new object
setForm({
...form,
firstName: 'Taylor'
});
¥Read updating objects in state and updating arrays in state to learn more.
例子 1 / 4: 表单(对象)
¥Form (object)
在此示例中,form
状态变量包含一个对象。每个输入都有一个更改处理程序,它使用整个表单的下一个状态调用 setForm
。{ ...form }
展开语法确保状态对象被替换而不是修改。
¥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.
import { useState } from 'react'; export default function Form() { const [form, setForm] = useState({ firstName: 'Barbara', lastName: 'Hepworth', email: 'bhepworth@sculpture.com', }); return ( <> <label> First name: <input value={form.firstName} onChange={e => { setForm({ ...form, firstName: e.target.value }); }} /> </label> <label> Last name: <input value={form.lastName} onChange={e => { setForm({ ...form, lastName: e.target.value }); }} /> </label> <label> Email: <input value={form.email} onChange={e => { setForm({ ...form, email: e.target.value }); }} /> </label> <p> {form.firstName}{' '} {form.lastName}{' '} ({form.email}) </p> </> ); }
避免重新创建初始状态
¥Avoiding recreating the initial state
React 会保存一次初始状态,并在下一次渲染时忽略它。
¥React saves the initial state once and ignores it on the next renders.
function TodoList() {
const [todos, setTodos] = useState(createInitialTodos());
// ...
尽管 createInitialTodos()
的结果仅用于初始渲染,但你仍会在每次渲染时调用此函数。如果要创建大型数组或执行昂贵的计算,这可能会造成浪费。
¥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.
为了解决这个问题,你可以将其作为初始化函数传递给 useState
:
¥To solve this, you may pass it as an initializer function to useState
instead:
function TodoList() {
const [todos, setTodos] = useState(createInitialTodos);
// ...
请注意,你传递的是 createInitialTodos
,它是函数本身,而不是 createInitialTodos()
,它是调用它的结果。如果你传递一个函数给 useState
,React 只会在初始化时调用它。
¥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.
React 可能在开发中 调用你的初始化器两次 以验证它们是 纯粹的。
¥React may call your initializers twice in development to verify that they are pure.
例子 1 / 2: 传递初始化函数
¥Passing the initializer function
这个例子传递了初始化函数,所以 createInitialTodos
函数只在初始化期间运行。它不会在组件重新渲染时运行,例如当你在输入中键入时。
¥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.
import { useState } from 'react'; function createInitialTodos() { const initialTodos = []; for (let i = 0; i < 50; i++) { initialTodos.push({ id: i, text: 'Item ' + (i + 1) }); } return initialTodos; } export default function TodoList() { const [todos, setTodos] = useState(createInitialTodos); const [text, setText] = useState(''); return ( <> <input value={text} onChange={e => setText(e.target.value)} /> <button onClick={() => { setText(''); setTodos([{ id: todos.length, text: text }, ...todos]); }}>Add</button> <ul> {todos.map(item => ( <li key={item.id}> {item.text} </li> ))} </ul> </> ); }
使用键来重置状态
¥Resetting state with a key
当 渲染列表 时,你会经常遇到 key
属性。但是,它还有另一个用途。
¥You’ll often encounter the key
attribute when rendering lists. However, it also serves another purpose.
你可以通过将不同的 key
传递给组件来重置组件的状态。在此示例中,“重置”按钮更改 version
状态变量,我们将其作为 key
传递给 Form
。当 key
改变时,React 从头开始重新创建 Form
组件(及其所有子级),因此它的状态被重置。
¥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.
阅读 保留和重置状态 以了解更多信息。
¥Read preserving and resetting state to learn more.
import { useState } from 'react'; export default function App() { const [version, setVersion] = useState(0); function handleReset() { setVersion(version + 1); } return ( <> <button onClick={handleReset}>Reset</button> <Form key={version} /> </> ); } function Form() { const [name, setName] = useState('Taylor'); return ( <> <input value={name} onChange={e => setName(e.target.value)} /> <p>Hello, {name}.</p> </> ); }
存储以前渲染的信息
¥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.
在大多数情况下,你不需要这样:
¥In most cases, you don’t need this:
-
如果你需要的值可以完全根据当前的属性或其他状态计算出来,那么 完全删除该冗余状态。 如果你担心重新计算过于频繁,那么
useMemo
钩子 可以提供帮助。¥If the value you need can be computed entirely from the current props or other state, remove that redundant state altogether. If you’re worried about recomputing too often, the
useMemo
Hook can help. -
如果你想重置整个组件树的状态,将不同的
key
传递给你的组件。¥If you want to reset the entire component tree’s state, pass a different
key
to your component. -
如果可以,请更新事件处理程序中的所有相关状态。
¥If you can, update all the relevant state in the event handlers.
在极少数情况下,这些都不适用,你可以使用一种模式根据目前已渲染的值更新状态,方法是在组件渲染时调用 set
函数。
¥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.
这是一个例子。这个 CountLabel
组件显示传递给它的 count
属性:
¥Here’s an example. This CountLabel
component displays the count
prop passed to it:
export default function CountLabel({ count }) {
return <h1>{count}</h1>
}
假设你想显示自上次更改以来计数器是增加还是减少。count
属性不会告诉你这一点 - 你需要跟踪它以前的值。添加 prevCount
状态变量来跟踪它。添加另一个名为 trend
的状态变量来保存计数是增加还是减少。比较 prevCount
和 count
,如果它们不相等,则同时更新 prevCount
和 trend
。现在你可以显示当前的计数属性以及自上次渲染以来它的变化情况。
¥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.
import { useState } from 'react'; export default function CountLabel({ count }) { const [prevCount, setPrevCount] = useState(count); const [trend, setTrend] = useState(null); if (prevCount !== count) { setPrevCount(count); setTrend(count > prevCount ? 'increasing' : 'decreasing'); } return ( <> <h1>{count}</h1> {trend && <p>The count is {trend}</p>} </> ); }
注意,如果你在渲染的时候调用了一个 set
函数,它必须是在一个像 prevCount !== count
这样的条件里面,并且在这个条件里面必须有一个像 setPrevCount(count)
这样的调用。否则,你的组件将在循环中重新渲染,直到它崩溃。此外,你只能像这样更新当前渲染组件的状态。渲染时调用其他组件的 set
函数报错。最后,你的 set
调用仍应为 无修改的更新状态 - 这并不意味着你可以违反 纯函数 的其他规则。
¥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 — this doesn’t mean you can break other rules of pure functions.
这种模式可能难以理解,通常最好避免使用。但是,它比在副作用中更新状态要好。当你在渲染期间调用 set
函数时,React 将在你的组件以 return
语句退出后立即重新渲染该组件,然后再渲染子级。这样,子级就不需要渲染两次。组件函数的其余部分仍将执行(结果将被丢弃)。如果你的条件低于所有的钩子调用,你可以添加一个早期的 return;
来更早地重新启动渲染。
¥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.
故障排除
¥Troubleshooting
我更新了状态,但日志记录给了我旧值
¥I’ve updated the state, but logging gives me the old value
调用 set
函数不会更改运行代码中的状态:
¥Calling the set
function does not change state in the running code:
function handleClick() {
console.log(count); // 0
setCount(count + 1); // Request a re-render with 1
console.log(count); // Still 0!
setTimeout(() => {
console.log(count); // Also 0!
}, 5000);
}
这是因为 状态的行为类似于快照。 更新状态请求另一个具有新状态值的渲染,但不会影响你已经运行的事件处理程序中的 count
JavaScript 变量。
¥This is because states behaves like 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.
如果需要使用下一个状态,可以将其保存在变量中,然后再传递给 set
函数:
¥If you need to use the next state, you can save it in a variable before passing it to the set
function:
const nextCount = count + 1;
setCount(nextCount);
console.log(count); // 0
console.log(nextCount); // 1
我已经更新了状态,但是屏幕没有更新
¥I’ve updated the state, but the screen doesn’t update
如果下一个状态等于前一个状态(由 Object.is
比较确定),React 将忽略你的更新。当你直接更改状态中的对象或数组时,通常会发生这种情况:
¥React will ignore your update if the next state is equal to the previous state, as determined by an Object.is
comparison. This usually happens when you change an object or an array in state directly:
obj.x = 10; // 🚩 Wrong: mutating existing object
setObj(obj); // 🚩 Doesn't do anything
你改变了一个现有的 obj
对象并将它传递回 setObj
,所以 React 忽略了更新。要解决此问题,你需要确保你始终是 替换状态中的对象和数组而不是改变它们:
¥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:
// ✅ Correct: creating a new object
setObj({
...obj,
x: 10
});
我收到错误:“太多的重新渲染”
¥I’m getting an error: “Too many re-renders”
你可能会收到一条错误消息:Too many re-renders. React limits the number of renders to prevent an infinite loop.
通常,这意味着你在渲染期间无条件设置状态,因此你的组件进入循环:渲染、设置状态(导致渲染)、渲染、设置状态(导致渲染),以此类推。通常,这是由于指定事件处理程序的错误造成的:
¥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:
// 🚩 Wrong: calls the handler during render
return <button onClick={handleClick()}>Click me</button>
// ✅ Correct: passes down the event handler
return <button onClick={handleClick}>Click me</button>
// ✅ Correct: passes down an inline function
return <button onClick={(e) => handleClick(e)}>Click me</button>
如果找不到此错误的原因,请单击控制台中错误旁边的箭头并查看 JavaScript 堆栈以查找导致错误的特定 set
函数调用。
¥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.
我的初始化或更新函数运行两次
¥My initializer or updater function runs twice
在 严格模式 中,React 将调用你的一些函数两次而不是一次:
¥In Strict Mode, React will call some of your functions twice instead of once:
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.
这种仅用于开发的行为可以帮助你 保持组件纯粹。 React 使用其中一个调用的结果,并忽略另一个调用的结果。只要你的组件、初始化器和更新器函数是纯函数,这就不会影响你的逻辑。但是,如果它们不小心不纯,这有助于你发现错误。
¥This development-only behavior helps you keep 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.
例如,这个不纯的更新函数改变了状态数组:
¥For example, this impure updater function mutates an array in state:
setTodos(prevTodos => {
// 🚩 Mistake: mutating state
prevTodos.push(createTodo());
});
因为 React 两次调用你的更新函数,你会看到 todo 被添加了两次,所以你会知道有一个错误。在这个例子中,你可以通过 替换数组而不是改变它 来修复错误:
¥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:
setTodos(prevTodos => {
// ✅ Correct: replacing with new state
return [...prevTodos, createTodo()];
});
既然这个更新函数是纯粹的,额外调用它不会对行为产生影响。这就是为什么 React 调用它两次可以帮助你发现错误。只有组件、初始化器和更新器函数需要是纯函数。事件处理程序不需要是纯粹的,因此 React 永远不会调用你的事件处理程序两次。
¥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.
阅读 保持组件纯粹 以了解更多信息。
¥Read keeping components pure to learn more.
我正在尝试将状态设置为一个函数,但它被调用了
¥I’m trying to set state to a function, but it gets called instead
你不能像这样将函数置于状态:
¥You can’t put a function into state like this:
const [fn, setFn] = useState(someFunction);
function handleClick() {
setFn(someOtherFunction);
}
因为你正在传递一个函数,React 假定 someFunction
是一个 初始化函数,而 someOtherFunction
是一个 更新函数,所以它会尝试调用它们并存储结果。要实际存储一个函数,在这两种情况下都必须将 () =>
放在它们之前。然后 React 将存储你传递的函数。
¥Because you’re passing a function, React assumes that someFunction
is an initializer function, and that someOtherFunction
is an updater function, 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.
const [fn, setFn] = useState(() => someFunction);
function handleClick() {
setFn(() => someOtherFunction);
}