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. 也许 state 变量看起来和一般的可读写的 JavaScript 变量类似。但 state 在其表现出的特性上更像是一张快照。设置它不会更改你已有的 state 变量,但会触发重新渲染。
你将会学习到
- How setting state triggers re-renders
- 设置 state 如何导致重新渲染
- When and how state updates
- state 在何时以何种方式更新
- Why state does not update immediately after you set it
- 为什么 state 不在设置后立即更新
- How event handlers access a “snapshot” of the state
- 事件处理函数如何获取 state 的一张“快照”
设置 state 会触发渲染 | Setting state triggers renders
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. 你可能会认为你的用户界面会直接对点击之类的用户输入做出响应并发生变化。在 React 中,它的工作方式与这种思维模型略有不同。在上一页中,你看到了来自 React 的设置 state 请求重新渲染。这意味着要使界面对输入做出反应,你需要设置其 state。
In this example, when you press “send”, setIsSent(true)
tells React to re-render the UI:
在这个例子中,当你按下 “send” 时,setIsSent(true)
会通知 React 重新渲染 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: 当你单击按钮时会发生以下情况:
- The
onSubmit
event handler executes.
- 执行
onSubmit
事件处理函数。
setIsSent(true)
setsisSent
totrue
and queues a new render.
setIsSent(true)
将isSent
设置为true
并排列一个新的渲染。
- React re-renders the component according to the new
isSent
value.
- React 根据新的
isSent
值重新渲染组件。
Let’s take a closer look at the relationship between state and rendering. 让我们仔细看看 state 和渲染之间的关系。
渲染会及时生成一张快照 | Rendering takes a snapshot in time
“Rendering” means that React is calling your component, which is a function. The JSX you return from that function is like a snapshot of the UI in time. Its props, event handlers, and local variables were all calculated using its state at the time of the render. “正在渲染” 就意味着 React 正在调用你的组件——一个函数。你从该函数返回的 JSX 就像是在某个时间点上 UI 的快照。它的 props、事件处理函数和内部变量都是 根据当前渲染时的 state 被计算出来的。
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. 与照片或电影画面不同,你返回的 UI “快照”是可交互的。它其中包括类似事件处理函数的逻辑,这些逻辑用于指定如何对输入作出响应。React 随后会更新屏幕来匹配这张快照,并绑定事件处理函数。因此,按下按钮就会触发你 JSX 中的点击事件处理函数。
When React re-renders a component: 当 React 重新渲染一个组件时:
- React calls your function again.
- React 会再次调用你的函数
- Your function returns a new JSX snapshot.
- 函数会返回新的 JSX 快照
- React then updates the screen to match the snapshot your function returned.
- React 会更新界面以匹配返回的快照
React 执行函数 | React executing the function 计算快照 | Calculating the snapshot 更新 DOM 树 | Updating the DOM tree
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! 作为一个组件的记忆,state 不同于在你的函数返回之后就会消失的普通变量。state 实际上“活”在 React 本身中——就像被摆在一个架子上!——位于你的函数之外。当 React 调用你的组件时,它会为特定的那一次渲染提供一张 state 快照。你的组件会在其 JSX 中返回一张包含一整套新的 props 和事件处理函数的 UI 快照 ,其中所有的值都是 根据那一次渲染中 state 的值 被计算出来的!
React 收到 setUpdate 通知 | You tell React to update the state React 更新 state 的值 | React updates the state value React 向组件内传入一张 state 的快照 | React passes a snapshot of the state value into the component
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”按钮会调用 setNumber(number + 1)
三次从而使计数器递增三次。
See what happens when you click the “+3” button: 看看你点击“+3”按钮时会发生什么:
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> </> ) }
Notice that number
only increments once per click!
请注意,每次点击只会让 number
递增一次!
Setting state only changes it for the next render. During the first render, number
was 0
. This is why, in that render’s onClick
handler, the value of number
is still 0
even after setNumber(number + 1)
was called:
设置 state 只会为下一次渲染变更 state 的值。在第一次渲染期间,number
为 0
。这也就解释了为什么在 那次渲染中的 onClick
处理函数中,即便在调用了 setNumber(number + 1)
之后,number
的值也仍然是 0
:
<button onClick={() => {
setNumber(number + 1);
setNumber(number + 1);
setNumber(number + 1);
}}>+3</button>
Here is what this button’s click handler tells React to do: 以下是这个按钮的点击事件处理函数通知 React 要做的事情:
setNumber(number + 1)
:number
is0
sosetNumber(0 + 1)
.
setNumber(number + 1)
:number
是0
所以setNumber(0 + 1)
。- React prepares to change
number
to1
on the next render. - React 准备在下一次渲染时将
number
更改为1
。
- React prepares to change
setNumber(number + 1)
:number
is0
sosetNumber(0 + 1)
.
setNumber(number + 1)
:number
是0
所以setNumber(0 + 1)
。- React prepares to change
number
to1
on the next render. - React 准备在下一次渲染时将
number
更改为1
。
- React prepares to change
setNumber(number + 1)
:number
is0
sosetNumber(0 + 1)
.
setNumber(number + 1)
:number
是0
所以setNumber(0 + 1)
。- React prepares to change
number
to1
on the next render. - React 准备在下一次渲染时将
number
更改为1
。
- React prepares to change
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
.
尽管你调用了三次 setNumber(number + 1)
,但在 这次渲染的 事件处理函数中 number
会一直是 0
,所以你会三次将 state 设置成 1
。这就是为什么在你的事件处理函数执行完以后,React 重新渲染的组件中的 number
等于 1
而不是 3
。
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:
你还可以通过在心里把 state 变量替换成它们在你代码中的值来想象这个过程。由于 这次渲染 中的 state 变量 number
是 0
,其事件处理函数看起来会像这样:
<button onClick={() => {
setNumber(0 + 1);
setNumber(0 + 1);
setNumber(0 + 1);
}}>+3</button>
For the next render, number
is 1
, so that render’s click handler looks like this:
对于下一次渲染来说,number
是 1
,因此 那次渲染中的 点击事件处理函数看起来会像这样:
<button onClick={() => {
setNumber(1 + 1);
setNumber(1 + 1);
setNumber(1 + 1);
}}>+3</button>
This is why clicking the button again will set the counter to 2
, then to 3
on the next click, and so on.
这就是为什么再次点击按钮会将计数器设置为 2
,下次点击时会设为 3
,依此类推。
随时间变化的 state | 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> </> ) }
If you use the substitution method from before, you can guess that the alert shows “0”: 如果你使用之前替换的方法,你就能猜到这个提示框将会显示 “0”:
setNumber(0 + 5);
alert(0);
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! 但如果你在这个提示框上加上一个定时器, 使得它在组件重新渲染 之后 才触发,又会怎样呢?是会显示 “0” 还是 “5” ?猜一猜!
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. 惊讶吗?你如果使用替代法,就能看到被传入提示框的 state “快照”。
setNumber(0 + 5);
setTimeout(() => {
alert(0);
}, 3000);
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! 到提示框运行时,React 中存储的 state 可能已经发生了更改,但它是使用用户与之交互时状态的快照进行调度的!
A state variable’s value never changes within a render, even if its event handler’s code is asynchronous. Inside that render’s onClick
, the value of number
continues to be 0
even after setNumber(number + 5)
was called. Its value was “fixed” when React “took the snapshot” of the UI by calling your component.
一个 state 变量的值永远不会在一次渲染的内部发生变化, 即使其事件处理函数的代码是异步的。在 那次渲染的 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: 这里有个示例能够说明上述特性会使你的事件处理函数更不容易出现计时错误。下面是一个会在五秒延迟之后发送一条消息的表单。想象以下场景:
- You press the “Send” button, sending “Hello” to Alice.
- 你按下“发送”按钮,向 Alice 发送“你好”。
- Before the five-second delay ends, you change the value of the “To” field to “Bob”.
- 在五秒延迟结束之前,将“To”字段的值更改为“Bob”。
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:
你觉得 alert
会显示什么?它是会显示“你向 Alice 说了你好“还是会显示“你向 Bob 说了你好”?根据你已经学到的知识猜一猜,然后动手试一试:
import { useState } from 'react'; export default function Form() { const [to, setTo] = useState('Alice'); const [message, setMessage] = useState('你好'); function handleSubmit(e) { e.preventDefault(); setTimeout(() => { alert(`你向 ${to} 说了${message}`); }, 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">发送</button> </form> ); }
React keeps the state values “fixed” within one render’s event handlers. You don’t need to worry whether the state has changed while the code is running. React 会使 state 的值始终“固定”在一次渲染的各个事件处理函数内部。你无需担心代码运行时 state 是否发生了变化。
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! 但是,万一你想在重新渲染之前读取最新的 state 怎么办?你应该使用 状态更新函数,下一页将会介绍!
摘要
- Setting state requests a new render.
- 设置 state 请求一次新的渲染。
- React stores state outside of your component, as if on a shelf.
- React 将 state 存储在组件之外,就像在架子上一样。
- When you call
useState
, React gives you a snapshot of the state for that render. - 当你调用
useState
时,React 会为你提供该次渲染 的一张 state 快照。 - Variables and event handlers don’t “survive” re-renders. Every render has its own event handlers.
- 变量和事件处理函数不会在重渲染中“存活”。每个渲染都有自己的事件处理函数。
- Every render (and functions inside it) will always “see” the snapshot of the state that React gave to that render.
- 每个渲染(以及其中的函数)始终“看到”的是 React 提供给这个 渲染的 state 快照。
- You can mentally substitute state in event handlers, similarly to how you think about the rendered JSX.
- 你可以在心中替换事件处理函数中的 state,类似于替换渲染的 JSX。
- Event handlers created in the past have the state values from the render in which they were created.
- 过去创建的事件处理函数拥有的是创建它们的那次渲染中的 state 值。
第 1 个挑战 共 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> </> ); }
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”.
向 click 事件处理函数添加一个 alert
。当灯为绿色且显示“Walk”时,单击按钮应显示“Stop is next”。当灯为红色并显示“Stop”时,单击按钮应显示“Walk is next”。
Does it make a difference whether you put the alert
before or after the setWalk
call?
把 alert
方法放在 setWalk
方法之前或之后有区别吗?