参考 React 19 官网

Actions

支持异步函数

  • 待定状态: 提供一个待定状态,该状态在请求开始时启动,并在最终状态更新提交时自动重置
  • 乐观更新: 支持新的 useOptimistic Hook,可以在请求提交时向用户显示即时反馈
  • 错误处理: 当请求失败时,可以显示错误边界,并自动将乐观更新恢复到其原始值
  • 表单:
    元素支持将函数传递给 action 和 formAction 属性。将函数传递给 action 属性默认使用 Actions,并在提交后自动重置表单
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 使用表单的 Actions 和 useActionState
function ChangeName({ name, setName }) {
const [error, submitAction, isPending] = useActionState(
async (previousState, formData) => {
const error = await updateName(formData.get("name"));
if (error) {
// 你可以返回操作的任何结果。
// 这里,我们只返回错误。
return error;
}
// 处理成功的情况。
return null;
},
null
);

return (
<form action={submitAction}>
<input type="text" name="name" />
<button type="submit" disabled={isPending}>
Update
</button>
{error && <p>{error}</p>}
</form>
);
}

useActionState

接受一个函数 (Action),并返回一个被包装的用于调用的 Action。这是因为 Actions 是可以组合的。当调用被包装的 Action 时,useActionState 将返回 Action 的最后结果作为 data,以及 Action 的待定状态作为 pending

form

<form> 功能集成在 react-dom 中。我们已经添加了对将函数作为 <form><input><button> 元素的 action 和 formAction 属性的支持,以便使用 Actions 自动提交表单
<form> Action 成功时,React 将自动为非受控组件重置表单。如果需要手动重置 <form>,你可以调用新的 requestFormReset React DOM API

1
<form action={actionFunction}>

useFormStatus

在设计系统中,常常需要编写设计一类能够访问其所在的 <form> 的信息而无需将属性传递到组件内的组件。这可以通过 Context 来实现,但为了使这类常见情况更简单,我们添加了一个新的 Hook useFormStatus

1
2
3
4
5
6
import { useFormStatus } from "react-dom";

function DesignButton() {
const { pending } = useFormStatus();
return <button type="submit" disabled={pending} />;
}

useOptimistic

执行数据变更时的另一个常见 UI 模式是在异步请求进行时乐观地显示最终状态
useOptimistic 会在 updateName 请求进行时立即渲染 optimisticName。当更新完成或出错时,React 将自动切换回 currentName 值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function ChangeName({ currentName, onUpdateName }) {
const [optimisticName, setOptimisticName] = useOptimistic(currentName);

const submitAction = async (formData) => {
const newName = formData.get("name");
setOptimisticName(newName);
const updatedName = await updateName(newName);
onUpdateName(updatedName);
};

return (
<form action={submitAction}>
<p>Your name is: {optimisticName}</p>
<p>
<label>Change Name:</label>
<input
type="text"
name="name"
disabled={currentName !== optimisticName}
/>
</p>
</form>
);
}

use

在渲染中读取资源。例如,可以使用 use 读取一个 promise,React 将挂起,直到 promise 解析完成

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import { use } from "react";

function Comments({ commentsPromise }) {
// `use` 将被暂停直到 promise 被解决.
const comments = use(commentsPromise);
return comments.map((comment) => <p key={comment.id}>{comment}</p>);
}

function Page({ commentsPromise }) {
// 当“use”在注释中暂停时,
// 将显示此悬念边界。
return (
<Suspense fallback={<div>Loading...</div>}>
<Comments commentsPromise={commentsPromise} />
</Suspense>
);
}

可以在循环和条件语句(如 if)中调用 use。但需要注意的是,调用 use 的函数仍然必须是一个组件或 Hook

1
2
3
4
5
6
7
8
9
10
11
12
13
import { use } from "react";
import ThemeContext from "./ThemeContext";

function Heading({ children }) {
if (children == null) {
return null;
}

// 因为过早的返回
// 这里 useContext 无法正常工作。
const theme = use(ThemeContext);
return <h1 style={{ color: theme.color }}>{children}</h1>;
}

读取 context

当 context 被传递给 use 时,它的工作方式类似于 useContext。而 useContext 必须在组件的顶层调用,use 可以在条件语句如 if 和循环如 for 内调用。相比之下,use 比 useContext 更加灵活

1
2
3
4
5
import { use } from 'react';

function Button() {
const theme = use(ThemeContext);
// ...

数据传递

数据可以通过将 Promise 作为 prop 从 服务器组件 传递到 客户端组件,以从服务器流式传输到客户端

将来自服务器组件的 Promise 传递至客户端组件时,其解析值必须可序列化以在服务器和客户端之间传递。像函数这样的数据类型不可序列化,不能成为这种 Promise 的解析值

1
2
3
4
5
6
7
8
9
10
11
import { fetchMessage } from "./lib.js";
import { Message } from "./message.js";

export default function App() {
const messagePromise = fetchMessage();
return (
<Suspense fallback={<p>waiting for message...</p>}>
<Message messagePromise={messagePromise} />
</Suspense>
);
}

客户端组件将 从 prop 中接收到的 Promise 传递给 use API。这允许客户端组件从最初由服务器组件创建的 Promise 中读取值

1
2
3
4
5
6
7
8
9
// message.js
"use client";

import { use } from "react";

export function Message({ messagePromise }) {
const messageContent = use(messagePromise);
return <p>Here is the message: {messageContent}</p>;
}

rejected Promise

传递给 use 的 Promise 可能会被拒绝(rejected)。可以通过以下方式处理 rejected Promise

不能在 try-catch 块中调用 use

ErrorBoundary

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
"use client";

import { use, Suspense } from "react";
import { ErrorBoundary } from "react-error-boundary";

export function MessageContainer({ messagePromise }) {
return (
<ErrorBoundary fallback={<p>⚠️Something went wrong</p>}>
<Suspense fallback={<p>⌛Downloading message...</p>}>
<Message messagePromise={messagePromise} />
</Suspense>
</ErrorBoundary>
);
}

function Message({ messagePromise }) {
const content = use(messagePromise);
return <p>Here is the message: {content}</p>;
}

Promise.catch

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import { Message } from "./message.js";

export default function App() {
const messagePromise = new Promise((resolve, reject) => {
reject();
}).catch(() => {
return "no new message found.";
});

return (
<Suspense fallback={<p>waiting for message...</p>}>
<Message messagePromise={messagePromise} />
</Suspense>
);
}

React DOM Static APIs

prerender

使用 Web 流将 React 树渲染为静态 HTML 字符串

1
const {prelude} = await prerender(reactNode, options?)

prerenderToNodeStream

使用 Node.js 流将 React 树渲染为静态 HTML 字符串

1
const {prelude} = await prerenderToNodeStream(reactNode, options?)

Server Components

一种全新的组件渲染模式,允许在打包前提前渲染组件,与客户端应用程序或 SSR 服务器在不同的环境中。这个独立的环境就是 React 服务器组件中的 “服务器”。服务器组件可以在你的 CI 服务器上在构建时运行一次,或者可以在每次请求时使用 web 服务器运行

  • 支持在构建时或请求时生成组件
  • 无需引入额外的工具链,即可与现有 React 项目集成

ref

属性

现在可以在函数组件中将 ref 作为 prop 进行访问,新的函数组件将不再需要 forwardRef。在未来的版本中,我们将弃用并移除 forwardRef
在类组件中,ref 不作为 props 传递,因为它们引用的是组件实例。这意味着,如果你在类组件中需要访问 ref,你需要使用 React.forwardRef 或者 React.createRef

函数清理

使得在 ref 改变时执行清理操作变得更加容易,当组件卸载时,React 将调用从 ref 回调返回的清理函数。例如,你可以在 ref 改变时取消订阅事件

1
2
3
4
5
6
7
8
9
10
11
<input
ref={(ref) => {
// ref 创建

// 新特性: 当元素从 DOM 中被移除时
// 返回一个清理函数来重置 ref
return () => {
// ref cleanup
};
}}
/>

Document Metadata

支持 <title><meta><link> 等文档元数据标签。这些标签可直接在组件中声明,React 会自动将它们提升至 <head>,并确保与服务端渲染和客户端渲染兼容,简化 SEO 和元数据管理逻辑

1
2
3
4
5
6
7
8
9
10
11
12
function BlogPost({ post }) {
return (
<article>
<h1>{post.title}</h1>
<title>{post.title}</title>
<meta name="author" content="Josh" />
<link rel="author" href="https://twitter.com/joshcstory/" />
<meta name="keywords" content={post.keywords} />
<p>Eee equals em-see-squared...</p>
</article>
);
}

样式表

增强了样式表的加载管理,通过指定 precedence 属性,React 可以动态调整样式表的插入顺序,确保正确的样式覆盖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function ComponentOne() {
return (
<Suspense fallback="loading...">
<link rel="stylesheet" href="foo" precedence="default" />
<link rel="stylesheet" href="bar" precedence="high" />
<article class="foo-class bar-class">
{...}
</article>
</Suspense>
)
}

function ComponentTwo() {
return (
<div>
<p>{...}</p>
{ /* will be inserted between foo & bar */}
<link rel="stylesheet" href="baz" precedence="default" />
</div>
)
}

Async 脚本

在 HTML 中,普通脚本 (<script src="...">) 和延迟脚本 (<script defer="" src="...">) 按照文档顺序加载,这使得在组件树深处渲染这些类型的脚本变得具有挑战性。然而,异步脚本 (<script async="" src="...">) 将去重并以任意顺序加载

1
2
3
4
5
6
7
8
function MyComponent() {
return (
<div>
<script async={true} src="..." />
Hello World
</div>
);
}

预加载

preload 和 preinit 指定浏览器提前加载的资源

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import { prefetchDNS, preconnect, preload, preinit } from "react-dom";

function MyComponent() {
// loads and executes this script eagerly
preinit("https://.../path/to/some/script.js", { as: "script" });
// preloads this font
preload("https://.../path/to/font.woff", { as: "font" });
// preloads this stylesheet
preload("https://.../path/to/stylesheet.css", { as: "style" });
// when you may not actually request anything from this host
prefetchDNS("https://...");
// when you will request something but aren't sure what
preconnect("https://...");
}

错误报告

改进了错误日志系统,减少了重复日志,并添加了更详细的调试信息。例如,对于 SSR 和客户端渲染不匹配的问题,提供了差异化日志

  • onCaughtError: 当 React 在错误边界中捕获错误时调用
  • onUncaughtError: 当抛出错误并且未被错误边界捕获时调用
  • onRecoverableError: 当抛出错误并自动恢复时调用

Context

可以将 <Context> 渲染为提供者,无需再使用 <Context.Provider>

1
2
3
4
5
const ThemeContext = createContext("");

function App({ children }) {
return <ThemeContext value="dark">{children}</ThemeContext>;
}