状态变量看起来可能像你可以读写的普通 JavaScript 变量。然而,状态的行为更像是快照。设置它并不会改变你已经拥有的状态变量,而是会触发重新渲染。
🌐 State variables might look like regular JavaScript variables that you can read and write to. However, state behaves more like a snapshot. Setting it does not change the state variable you already have, but instead triggers a re-render.
你将学习到
- 设置状态如何触发重新渲染
- 状态何时以及如何更新
- 为什么设置后状态不会立即更新
- 事件处理程序如何访问状态的“快照”
设置状态触发渲染
🌐 Setting state triggers renders
你可能会认为你的用户界面会直接响应用户事件(例如点击)而发生变化。在 React 中,它的工作方式与这种心理模型略有不同。在上一页中,你看到 设置状态会请求 React 重新渲染。这意味着为了让界面响应事件,你需要更新状态。
🌐 You might think of your user interface as changing directly in response to the user event like a click. In React, it works a little differently from this mental model. On the previous page, you saw that setting state requests a re-render from React. This means that for an interface to react to the event, you need to update the state.
在此示例中,当你按下“发送”时,setIsSent(true) 会告诉 React 重新渲染用户界面:
🌐 In this example, when you press “send”, setIsSent(true) tells React to re-render the UI:
import { useState } from 'react'; export default function Form() { const [isSent, setIsSent] = useState(false); const [message, setMessage] = useState('Hi!'); if (isSent) { return <h1>Your message is on its way!</h1> } return ( <form onSubmit={(e) => { e.preventDefault(); setIsSent(true); sendMessage(message); }}> <textarea placeholder="Message" value={message} onChange={e => setMessage(e.target.value)} /> <button type="submit">Send</button> </form> ); } function sendMessage(message) { // ... }
单击按钮时会发生以下情况:
🌐 Here’s what happens when you click the button:
onSubmit事件处理程序执行。setIsSent(true)将isSent设置为true并排队一个新的渲染。- React 会根据新的
isSent值重新渲染组件。
让我们仔细看看状态和渲染之间的关系。
🌐 Let’s take a closer look at the relationship between state and rendering.
渲染及时拍快照
🌐 Rendering takes a snapshot in time
“渲染”意味着 React 正在调用你的组件,这个组件是一个函数。你从该函数返回的 JSX 就像是 UI 的一个时间快照。它的 props、事件处理程序和局部变量都是使用渲染时的状态计算的。
与照片或电影画面不同,你返回的 UI “快照”是可交互的。它包含像事件处理程序这样的逻辑,这些逻辑指定了对输入的响应。React 会更新屏幕以匹配此快照并连接事件处理程序。因此,按下按钮将触发你在 JSX 中的点击处理程序。
🌐 Unlike a photograph or a movie frame, the UI “snapshot” you return is interactive. It includes logic like event handlers that specify what happens in response to inputs. React updates the screen to match this snapshot and connects the event handlers. As a result, pressing a button will trigger the click handler from your JSX.
当 React 重新渲染一个组件时:
🌐 When React re-renders a component:
- React 再次调用你的函数。
- 你的函数返回一个新的 JSX 快照。
- 然后 React 更新屏幕以匹配函数返回的快照。

React executing the function 
Calculating the snapshot 
Updating the DOM tree
Illustrated by Rachel Lee Nabors
作为组件的记忆,状态并不像普通变量那样在函数返回后消失。状态实际上“存在”于 React 本身——就像放在架子上一样——在你的函数之外。当 React 调用你的组件时,它会给你该特定渲染的状态快照。你的组件返回 UI 的快照,其中的 JSX 包含一套新的 props 和事件处理程序,所有这些都是 使用该渲染的状态值计算的!
🌐 As a component’s memory, state is not like a regular variable that disappears after your function returns. State actually “lives” in React itself—as if on a shelf!—outside of your function. When React calls your component, it gives you a snapshot of the state for that particular render. Your component returns a snapshot of the UI with a fresh set of props and event handlers in its JSX, all calculated using the state values from that render!

You tell React to update the state 
React updates the state value 
React passes a snapshot of the state value into the component
Illustrated by Rachel Lee Nabors
这里有一个小实验来向你展示这是如何工作的。在这个例子中,你可能会期望点击“+3”按钮会使计数器增加三次,因为它调用了 setNumber(number + 1) 三次。
🌐 Here’s a little experiment to show you how this works. In this example, you might expect that clicking the “+3” button would increment the counter three times because it calls setNumber(number + 1) three times.
看看当你点击“+3”按钮时会发生什么:
🌐 See what happens when you click the “+3” button:
import { useState } from 'react'; export default function Counter() { const [number, setNumber] = useState(0); return ( <> <h1>{number}</h1> <button onClick={() => { setNumber(number + 1); setNumber(number + 1); setNumber(number + 1); }}>+3</button> </> ) }
注意 number 每次点击只增加一次!
🌐 Notice that number only increments once per click!
设置状态只会更改下一次渲染的值。 在第一次渲染时,number 是 0。这就是为什么在那次渲染的 onClick 处理函数中,即使调用了 setNumber(number + 1),number 的值仍然是 0:
<button onClick={() => {
setNumber(number + 1);
setNumber(number + 1);
setNumber(number + 1);
}}>+3</button>以下是此按钮的点击处理程序告诉 React 执行的操作:
🌐 Here is what this button’s click handler tells React to do:
setNumber(number + 1):number是0所以setNumber(0 + 1)。- React 准备在下一次渲染时将
number更改为1。
- React 准备在下一次渲染时将
setNumber(number + 1):number是0所以setNumber(0 + 1)。- React 准备在下一次渲染时将
number更改为1。
- React 准备在下一次渲染时将
setNumber(number + 1):number是0所以setNumber(0 + 1)。- React 准备在下一次渲染时将
number更改为1。
- React 准备在下一次渲染时将
即使你调用了 setNumber(number + 1) 三次,在这个渲染的事件处理器中 number 总是 0,所以你将状态设置为 1 三次。这就是为什么在你的事件处理器完成后,React 重新渲染组件时 number 等于 1 而不是 3。
🌐 Even though you called setNumber(number + 1) three times, in this render’s event handler number is always 0, so you set the state to 1 three times. This is why, after your event handler finishes, React re-renders the component with number equal to 1 rather than 3.
你也可以通过在脑海中用它们在代码中的数值替换状态变量来进行可视化。由于 number 状态变量在这次渲染中是 0,它的事件处理器看起来像这样:
🌐 You can also visualize this by mentally substituting state variables with their values in your code. Since the number state variable is 0 for this render, its event handler looks like this:
<button onClick={() => {
setNumber(0 + 1);
setNumber(0 + 1);
setNumber(0 + 1);
}}>+3</button>对于下一次渲染,number 是 1,所以那次渲染的点击处理器看起来像这样:
🌐 For the next render, number is 1, so that render’s click handler looks like this:
<button onClick={() => {
setNumber(1 + 1);
setNumber(1 + 1);
setNumber(1 + 1);
}}>+3</button>这就是为什么再次点击按钮会将计数器设置为 2,然后在下次点击时设置为 3,以此类推。
🌐 This is why clicking the button again will set the counter to 2, then to 3 on the next click, and so on.
随时间变化的状态
🌐 State over time
嗯,那很有趣。试着猜猜点击这个按钮会弹出什么警告:
🌐 Well, that was fun. Try to guess what clicking this button will alert:
import { useState } from 'react'; export default function Counter() { const [number, setNumber] = useState(0); return ( <> <h1>{number}</h1> <button onClick={() => { setNumber(number + 5); alert(number); }}>+5</button> </> ) }
如果你使用之前的代入法,你可以猜测警报显示“0”
🌐 If you use the substitution method from before, you can guess that the alert shows “0”:
setNumber(0 + 5);
alert(0);但是,如果你在警报上设置一个定时器,让它只在组件重新渲染之后触发呢?它会显示“0”还是“5”?来猜猜看!
🌐 But what if you put a timer on the alert, so it only fires after the component re-rendered? Would it say “0” or “5”? Have a guess!
import { useState } from 'react'; export default function Counter() { const [number, setNumber] = useState(0); return ( <> <h1>{number}</h1> <button onClick={() => { setNumber(number + 5); setTimeout(() => { alert(number); }, 3000); }}>+5</button> </> ) }
惊讶吗?如果你使用替换方法,你可以看到传递给警报的状态的“快照”。
🌐 Surprised? If you use the substitution method, you can see the “snapshot” of the state passed to the alert.
setNumber(0 + 5);
setTimeout(() => {
alert(0);
}, 3000);警报运行时存储在 React 中的状态可能已经改变,但它是使用用户与之交互时的状态快照来安排的!
🌐 The state stored in React may have changed by the time the alert runs, but it was scheduled using a snapshot of the state at the time the user interacted with it!
**状态变量的值在一次渲染中永远不会改变,**即使它的事件处理程序的代码是异步的。在那次渲染的 onClick 中,number 的值即使在调用 setNumber(number + 5) 之后仍然保持为 0。当 React 通过调用你的组件“拍摄 UI 快照”时,其值就被“固定”了。
这是一个示例,说明这如何使你的事件处理程序不容易出现时序错误。下面是一个表单,它会在五秒延迟后发送消息。想象一下这种情况:
🌐 Here is an example of how that makes your event handlers less prone to timing mistakes. Below is a form that sends a message with a five-second delay. Imagine this scenario:
- 你按下“发送”按钮,把“你好”发送给爱丽丝。
- 在五秒延迟结束之前,你将“收件人”字段的值更改为“Bob”。
你期望 alert 显示什么?它会显示“你对 Alice 说了 Hello”吗?还是会显示“你对 Bob 说了 Hello”?根据你所知道的做一个猜测,然后试一试:
🌐 What do you expect the alert to display? Would it display, “You said Hello to Alice”? Or would it display, “You said Hello to Bob”? Make a guess based on what you know, and then try it:
import { useState } from 'react'; export default function Form() { const [to, setTo] = useState('Alice'); const [message, setMessage] = useState('Hello'); function handleSubmit(e) { e.preventDefault(); setTimeout(() => { alert(`You said ${message} to ${to}`); }, 5000); } return ( <form onSubmit={handleSubmit}> <label> To:{' '} <select value={to} onChange={e => setTo(e.target.value)}> <option value="Alice">Alice</option> <option value="Bob">Bob</option> </select> </label> <textarea placeholder="Message" value={message} onChange={e => setMessage(e.target.value)} /> <button type="submit">Send</button> </form> ); }
React 会在一次渲染的事件处理器中将状态值“固定”。 当代码运行时,你不需要担心状态是否已经改变。
但是如果你想在重新渲染之前读取最新的状态呢?你会想要使用一个状态更新函数,将在下一页讲解!
🌐 But what if you wanted to read the latest state before a re-render? You’ll want to use a state updater function, covered on the next page!
回顾
- 设置状态请求新的渲染。
- React 将状态存储在组件外部,就像束之高阁一样。
- 当你调用
useState时,React 会给你一个 该渲染 的状态快照。 - 变量和事件处理程序不会在重新渲染中“存活”。每次渲染都有其自己的事件处理程序。
- 每次渲染(以及其中的函数)总是会“看到” React 提供给该渲染的状态快照。
- 你可以在心理上替换事件处理程序中的状态,类似于你对渲染的 JSX 的看法。
- 过去创建的事件处理程序具有创建它们时所在的渲染中的状态值。
挑战 1 of 1: 实现红绿灯
🌐 Implement a traffic light
这是一个在按下按钮时切换的人行横道灯组件:
🌐 Here is a crosswalk light component that toggles when the button is pressed:
import { useState } from 'react'; export default function TrafficLight() { const [walk, setWalk] = useState(true); function handleClick() { setWalk(!walk); } return ( <> <button onClick={handleClick}> Change to {walk ? 'Stop' : 'Walk'} </button> <h1 style={{ color: walk ? 'darkgreen' : 'darkred' }}> {walk ? 'Walk' : 'Stop'} </h1> </> ); }
在点击处理程序中添加一个 alert。当灯是绿的并显示“走”时,点击按钮应该显示“接下来是停”。当灯是红的并显示“停”时,点击按钮应该显示“接下来是走”。
🌐 Add an alert to the click handler. When the light is green and says “Walk”, clicking the button should say “Stop is next”. When the light is red and says “Stop”, clicking the button should say “Walk is next”.
把 alert 放在 setWalk 调用之前还是之后,会有什么区别吗?
🌐 Does it make a difference whether you put the alert before or after the setWalk call?