React + TypeScript: 以快照的形式呈现状态

React官方网站的文档已于2023年3月16日进行了修订(请参阅“Introducing react.dev”)。本文摘要整理了基本解释中的“State as a Snapshot”并添加了TypeScript代码。但是,我们省略了针对初学者的JavaScript基础说明。

此外,关于本系列解说的其他文章,请参考「React + TypeScript:学习React官方文档基础解说『Learn React』」。

状态变量可能会让人以为它们像标准JavaScript变量一样可以读写值。然而,状态的行为更接近于“快照”(作者注:记录当前信息的快照)。状态的设置并不是对预先确定的值进行更改,而是重新触发再渲染。

根据状态设定启动渲染

在React中,用户界面不会直接根据交互事件等进行更新。当状态发生改变时,React会重新渲染组件。相反,要显示与事件相应的界面,需要更新状态。

例如,在以下的代码示例中,当点击表单(Form)的[发送]按钮时,状态变量(isSent)会被更新,React会重新渲染UI(示例001)。

import { ChangeEventHandler, FormEventHandler, useState } from 'react';
import { Form } from './Form';
import { SendMessage } from './SendMessage';

const sendMessage = (message: string) => {
	// ...
};
export default function App() {
	const [isSent, setIsSent] = useState(false);
	const [message, setMessage] = useState<any>('Hi!');
	const handleChange: ChangeEventHandler<HTMLTextAreaElement> = ({
		target: { value }
	}) => {
		setMessage(value);
	};
	const handleSubmit: FormEventHandler<HTMLFormElement> = (event) => {
		event.preventDefault();
		setIsSent(true);
		sendMessage(message);
	};
	return isSent ? (
		<SendMessage />
	) : (
		<Form
			handleChange={handleChange}
			handleSubmit={handleSubmit}
			message={message}
		/>
	);
}
import { ChangeEventHandler, FC, FormEventHandler } from 'react';

type Props = {
	handleChange: ChangeEventHandler<HTMLTextAreaElement>;
	handleSubmit: FormEventHandler<HTMLFormElement>;
	message: string;
};
export const Form: FC<Props> = ({ handleChange, handleSubmit, message }) => {
	return (
		<form onSubmit={handleSubmit}>
			<textarea placeholder="Message" value={message} onChange={handleChange} />
			<button type="submit">Send</button>
		</form>
	);
};

React + TypeScript: 以快照形式管理状态的示例001

 

让我们按顺序逐步确认点击按钮后发生了什么。

    1. 首先会调用onSubmit事件处理程序。

 

    1. 在那里执行的是状态变量设置函数setIsSent。新的值(true)将被加入到渲染队列中。

 

    然后React会根据已更新的状态变量值(isSent)重新渲染组件。

渲染是在那个时刻拍摄的快照。

「渲染」是指React调用函数组件。该函数返回的JSX可以说是当前UI的快照。属性、事件处理程序和局部变量都根据渲染时的状态进行计算。

返回的UI快照是交互式的。因此,它包含了根据输入决定触发什么的事件处理等逻辑。因此,React会根据这个快照更新屏幕,并将其连接到事件处理程序上。结果是,例如,当按下按钮时,JSX的点击处理程序会被调用。

以下是使用React进行组件重新渲染的步骤:

    1. 函数执行:React再次调用函数。

计算快照:函数返回的是新的JSX快照。

更新DOM树:React根据返回值的快照来更新界面。

对于组件来说,与通常的变量不同,作为组件的记忆角色的状态不会在函数处理完后消失。实际上,这些状态是由React本身持有在函数外部。当React调用组件时,获取到的是应该用于渲染的状态快照。而组件返回的UI快照包含了最新的属性和事件处理器,它们都包含在JSX中。所有这些都是基于该渲染的状态值进行计算的。

为了理解状态快照,让我们考虑下面代码的结果。在按钮(

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);
					alert(number);
				}}
			>
				+3
			</button>
		</>
	);
}

当我第一次点击按钮并查看结果时,显示的计数器状态变量值(number)为1。也就是说,只增加了从初始值0到1的加法。而且,警告对话框中显示状态变量值为0。

在渲染时设置的状态变量只是用于下一次渲染的值。渲染时会以最近的状态变量值(0)为基础进行渲染。因此,无论通过设置函数(setNumber)对状态变量(number)进行多少次累加,它都不会改变,仍然保持最近的值不变。

时间的流逝与状态

在上述的代码示例中,状态变量保持初始值(0)可能是因为立即打开警告对话框的原因。因此,我们可以将其改写为稍微延迟一些时间再进行确认。

export default function Counter() {

	return (
		<>
			<h1>{number}</h1>
			<button
				onClick={() => {
					setNumber(number + 1);
					setNumber(number + 1);
					setNumber(number + 1);
					// alert(number);
					setTimeout(() => {
						alert(number);
					}, 3000);
				}}
			>
				+3
			</button>
		</>
	);
}

然而,显示的值仍然保持初始值不变。当React通过调用组件来获取UI的快照时,状态值会被“固定”。即使将代码设为异步,状态变量的值在该渲染期间也不会改变。

在设置状态变量时延迟一下,看看会怎样。我们将在下面的代码示例(Sample 002)中进行确认。

import { useState } from 'react';
import './styles.css';

export default function Counter() {
	const [number, setNumber] = useState(0);
	return (
		<>
			<h1>{number}</h1>
			<button
				onClick={() => {
					setNumber(number + 1);
					setNumber(number + 1);
					setNumber(number + 1);
					setTimeout(() => {
						setNumber(number + 10);
					}, 1000);
					setTimeout(() => {
						alert(number);
					}, 3000);
				}}
			>
				+3
			</button>
		</>
	);
}

样例002 ■ React + TypeScript: 状态作为快照02

 

画面上,计数器的数值会稍有延迟地变化(从1到10)。然而,警告对话框中显示的仍然是初始值(0)。此外,设置后的计数器值(10)作为状态变量值(number)被引用时也可以看出其是初始值。

在React中,对于一个渲染的事件处理程序,它会“固定”状态变量的值。这意味着在代码执行期间不必担心状态的变化。

总结

在本文中,我对以下几个项目进行了解释。

    • 状態を設定すると新たなレンダーが求められます。

 

    • Reactが状態を保持するのは関数の外です。

useStateを呼び出すと、Reactからそのレンダーにおける状態のスナップショットが得られます。
変数やイベントハンドラは再レンダー間で同じではありません。レンダーごとに備わるのはそれぞれ独自のイベントハンドラです。
各レンダー(およびその中の関数)はつねに、Reactがそのレンダーに与えた状態のスナップショットを参照します。
イベントハンドラが参照するのはそれがつくられたレンダーの状態変数値です。

广告
将在 10 秒后关闭
bannerAds