状态作为快照
状态变量可能看起来像您可以读取和写入的常规 JavaScript 变量。 但是,状态的行为更像是快照。 设置它不会更改您已有的状态变量,而是会触发重新渲染。
你将学习
- 设置状态如何触发重新渲染
- 状态何时以及如何更新
- 为什么设置后状态不会立即更新
- 事件处理程序如何访问状态的“快照”
设置状态触发渲染
您可能认为您的用户界面会直接响应用户事件(如点击)而发生变化。 在 React 中,它的工作方式与这种心智模型略有不同。 在上一页中,您看到设置状态请求 React 重新渲染。 这意味着要让界面对事件做出反应,您需要更新状态。
在此示例中,当您按下“发送”时,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) {
// ...
}
单击按钮时会发生以下情况:
- onSubmit 事件处理程序执行。
- setIsSent(true) 将 isSent 设置为 true 并将新渲染排队。
- React 根据新的 isSent 值重新渲染组件。
让我们仔细看看状态和渲染之间的关系。
渲染马上得到快照
“渲染”意味着 React 正在调用你的组件,这是一个函数。 从该函数返回的 JSX 就像 UI 的即时快照。 它的props、事件处理程序和局部变量都是使用渲染时的状态计算的。
与照片或电影画面不同,您返回的 UI“快照”是交互式的。 它包括类似事件处理程序的逻辑,用于指定响应输入时发生的事情。 React 然后更新屏幕以匹配此快照并连接事件处理程序。 因此,按下按钮将触发来自 JSX 的点击处理程序。
当 React 重新渲染一个组件时:
- React 再次调用你的函数。
- 您的函数返回一个新的 JSX 快照。
- React 然后更新屏幕以匹配您返回的快照。
作为组件的内存,state 不像一个在函数返回后消失的常规变量。 State 实际上“存在于”React 本身——就像在架子上一样!——在你的函数之外。 当 React 调用您的组件时,它会为您提供该特定渲染的状态快照。 您的组件在其 JSX 中返回 UI 的快照,其中包含一组新的道具和事件处理程序,所有这些都是使用该渲染中的状态值计算的!
这是一个小实验,向您展示这是如何工作的。 在此示例中,您可能希望单击“+3”按钮会使计数器递增三次,因为它调用了 setNumber(number + 1) 三次。
看看当你点击“+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>
</>
)
}
请注意,每次点击数字只会增加一次!
设置状态只会为下一次渲染更改它。 在第一次渲染期间,number 为 0。这就是为什么在该渲染的 onClick 处理程序中,即使在调用 setNumber(number + 1) 之后 number 的值仍然为 0:
<button onClick={() => {
setNumber(number + 1);
setNumber(number + 1);
setNumber(number + 1);
}}>+3</button>
以下是此按钮的点击处理程序告诉 React 执行的操作:
- setNumber(number + 1): number 为 0 所以 setNumber(0 + 1)。
- React 准备在下一次渲染时将数字更改为 1。
- setNumber(number + 1): number 为 0 所以 setNumber(0 + 1)。
- React 准备在下一次渲染时将数字更改为 1。
- setNumber(number + 1): number 为 0 所以 setNumber(0 + 1)。
- React 准备在下一次渲染时将数字更改为 1。
即使您调用了 setNumber(number + 1) 三次,在此渲染的事件处理程序中,编号始终为 0,因此您将状态设置为 1 三次。 这就是为什么在事件处理程序完成后,React 会重新渲染编号等于 1 而不是 3 的组件。
您还可以通过在代码中用状态变量的值在心里替换它们来形象化这一点。 由于此渲染的数字状态变量为 0,因此其事件处理程序如下所示:
<button onClick={() => {
setNumber(0 + 1);
setNumber(0 + 1);
setNumber(0 + 1);
}}>+3</button>
对于下一个渲染,数字为 1,因此渲染的点击处理程序如下所示:
<button onClick={() => {
setNumber(1 + 1);
setNumber(1 + 1);
setNumber(1 + 1);
}}>+3</button>
这就是为什么再次单击该按钮会将计数器设置为 2,然后在下一次单击时设置为 3,依此类推。
随着时间的状态推移
好吧,那很有趣。 尝试猜测点击这个按钮会提示什么:
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>
</>
)
}
如果您使用之前的替换方法,您可以猜测alert显示为“0”:
setNumber(0 + 5);
alert(0);
但是,如果您将计时器置于alert状态,使其仅在组件重新渲染后触发怎么办? 它会说“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>
</>
)
}
惊讶吗? 如果您使用替换方法,您可以看到传递给alert的状态的“快照”。
setNumber(0 + 5);
setTimeout(() => {
alert(0);
}, 3000);
alert运行时存储在 React 中的状态可能已经改变,但它是使用用户与之交互时的状态快照来安排的!
状态变量的值在渲染中永远不会改变,即使它的事件处理程序的代码是异步的。 在该渲染器的 onClick 中,即使在调用 setNumber(number + 5) 之后,number 的值仍然为 0。 当 React 通过调用您的组件“获取 UI 的快照”时,它的值是“固定的”。
下面是一个示例,说明这如何使您的事件处理程序更不容易出现计时错误。 下面是一个以五秒延迟发送消息的表单。 想象一下这个场景:
您按下“发送”按钮,向爱丽丝发送“你好”。
在五秒延迟结束之前,您将“To”字段的值更改为“Bob”。
您希望警报显示什么? 它会显示“你对爱丽丝说你好”吗? 或者它会显示“你向 Bob 打招呼”? 根据您所知道的进行猜测,然后尝试:
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 在一个渲染的事件处理程序中保持状态值“固定”。 您无需担心代码运行时状态是否发生了变化。
但是如果你想在重新渲染之前读取最新状态怎么办? 你会想要使用状态更新函数,在下一页介绍!
回顾
- 设置状态请求新的渲染。
- React 将状态存储在组件外部,就像在架子上一样。
- 当您调用 useState 时,React 会为您提供该渲染的状态快照。
- 变量和事件处理程序不会“幸免于”重新渲染。 每个渲染器都有自己的事件处理程序。
- 每个渲染器(以及其中的函数)将始终“看到”React 提供给该渲染器的状态快照。
- 您可以在心里替换事件处理程序中的状态,类似于您对呈现的 JSX 的看法。
- 过去创建的事件处理程序具有创建它们时所在的渲染中的状态值。
网友评论