这篇文章主要介绍了 React 19 的新功能,包括 Actions 自动处理数据突变相关状态,新增的 useActionState、useFormStatus、useOptimistic 等 hook,新的 use API 读取渲染资源,新的 React DOM 静态 API 生成静态站点,以及 React 服务器组件中的 RSC 和 RSA 等内容。同时提到后续会分享针对旧版功能的优化和改良。
关联问题: React 19 性能如何 RSC 如何使用 Actions 适用场景
00. Hello World
大家好,我是大家的 林语冰
。
React 18 发布两年半后,React 团队终于官宣,React 19 稳定版正式升级。
01. React 19 新功能
1.1 Action
React 应用的常见用例之一是执行数据突变,然后更新状态作为响应。
比如,用户提交表单改名时,你会发出请求,然后处理响应。过去,你需要手动处理挂起状态、错误、乐观更新和串行请求。
举个栗子,以前可以在 useState
中处理挂起和错误状态:
1function UpdateName({}) {
2
3 const [isPending, setIsPending] = useState(false)
4
5 const handleSubmit = async () => {
6 setIsPending(true)
7 const error = await updateName(name)
8 setIsPending(false)
9 if (error) {
10 setError(error)
11 return
12 }
13 redirect('/path')
14 }
15
16}
React 19 支持在转换中使用异步函数,自动处理挂起状态、错误、表单和乐观更新。
现在,可以使用 useTransition
来处理挂起状态:
1function UpdateName({}) {
2
3 const [isPending, startTransition] = useTransition()
4
5 const handleSubmit = () => {
6 startTransition(async () => {
7 const error = await updateName(name)
8 if (error) {
9 setError(error)
10 return
11 }
12 redirect('/path')
13 })
14 }
15
16}
异步转换会立即将 isPending
的状态设置为 true
,发出异步请求,并在任何转换后将 isPending
切换为 false
。
按照惯例,使用异步转换的函数称为“Actions”(动作)。
Actions 会自动管理提交的数据:
- 挂起状态:Actions 提供挂起状态,该状态在请求开始时启动,并在提交最终状态更新时自动重置。
- 乐观更新:Actions 新增
useOptimistic
hook,你可以在提交请求时向用户显示即时反馈。 - 错误处理:Actions 提供错误处理,在请求失败时显示错误边界,并自动将乐观更新恢复为其原始值。
- 表单:
<form>
元素现在支持将函数传递给action
和formAction
props。将函数传递给action
props 默认使用 Actions,并在提交后自动重置表单。
React 19 构建于 Actions 之上,引入 useOptimistic
管理乐观更新,并新增了 React.useActionState
hook 来处理 Actions 的常见情况。
react-dom
添加了 <form>
Actions 自动管理表单,以及 useFormStatus
来支持表单动作的常见情况。
React 19 可以简化上述例子:
1function ChangeName({ name, setName }) {
2 const [error, submitAction, isPending] = useActionState(
3 async (previousState, formData) => {
4 const error = await updateName(formData.get('name'))
5 if (error) return error
6 redirect('/path')
7 return null
8 },
9 null
10 )
11
12 return <form action={submitAction}>// ...</form>
13}
1.2 新 hook:useActionState
我们新增了 useActionState
hook:
1const [error, submitAction, isPending] = useActionState(
2 async (previousState, newName) => {
3 const error = await updateName(newName)
4 if (error) {
5
6
7 return error
8 }
9
10 return null
11 },
12 null
13)
useActionState
接受一个 Action 函数,并返回一个包装 Action 来调用。
当调用包装 Action 时,useActionState
会返回 Action 的结果作为 data
,并将其挂起状态返回为 pending
。
1.3 <form>
Actions
React 19 还把 Actions 与 react-dom
的 <form>
新功能集成,支持传递函数作为 <form>
、<input>
和 <button>
元素的 action
和 formAction
props,自动提交带有 Actions 的表单:
1<form action={actionFunction}>
当 <form>
Action 成功时,React 会自动重置非受控组件的表单。如果需要手动重置 <form>
,可以调用新的 React DOM requestFormReset
API。
1.4 新 hook:useFormStatus
在设计系统中,通常会编写需要访问其所在 <form>
信息的设计组件,而无需将 props
逐层传递到组件。
这可以通过 Context 来完成,但我们新增了一个简化 hook useFormStatus
:
1import { useFormStatus } from 'react-dom'
2
3function DesignButton() {
4 const { pending } = useFormStatus()
5 return <button type="submit" disabled={pending} />
6}
useFormStatus
读取父级 <form>
的状态,就好像该表单是 Context provider 一样。
1.5 新 hook:useOptimistic
执行数据突变的另一种常见 UI 模式是在异步请求正在进行时乐观显示最终状态。
React 19 新增了一个 useOptimistic
简化 hook:
1function ChangeName({ currentName, onUpdateName }) {
2 const [optimisticName, setOptimisticName] = useOptimistic(currentName)
3
4 const submitAction = async (formData) => {
5 const newName = formData.get('name')
6 setOptimisticName(newName)
7 const updatedName = await updateName(newName)
8 onUpdateName(updatedName)
9 }
10
11 return (
12 <form action={submitAction}>
13 <p>{optimisticName}</p>
14 <input
15 name="name"
16 disabled={currentName !== optimisticName}
17 />
18 </form>
19 )
20}
当 updateName
请求正在进行时,useOptimistic
hook 会立即渲染 optimisticName
。当更新完成或出错时,React 会自动切换回 currentName
的值。
1.6 新 API:use
React 19 新增 use
API 来读取渲染中的资源。
举个栗子,你可以使用 use
读取 promise 对象,React 会挂起直到该 promise 解析:
1import { use } from 'react'
2
3function Comments({ commentsPromise }) {
4
5 const comments = use(commentsPromise)
6 return comments.map((comment) => <p key={comment.id}>{comment}</p>)
7}
8
9function Page({ commentsPromise }) {
10
11
12 return (
13 <Suspense fallback={<div>Loading...</div>}>
14 <Comments commentsPromise={commentsPromise} />
15 </Suspense>
16 )
17}
use
不支持渲染中创建的 promise 对象。如果你将渲染中创建的 promise 传递给 use
,React 会报错。
你还可以使用 use
条件式读取 context,比如在提前 return 之后:
1import { use } from 'react'
2import ThemeContext from './ThemeContext'
3
4function Heading({ children }) {
5 if (children == null) {
6 return null
7 }
8
9
10 const theme = use(ThemeContext)
11 return <h1 style={{ color: theme.color }}>{children}</h1>
12}
类似于 hooks,use
API 只能在渲染中调用。与 hooks 不同的是,use
可以条件调用。
02. 新的 React DOM 静态 API
react-dom/static
新增了两个 API 来生成静态站点:
prerender
prerenderToNodeStream
这些新 API 通过等待数据加载以生成静态 HTML 来改进 renderToString
,为了与 Node Streams 和 Web Streams 等流环境配合使用。
举个栗子,在 Web Stream 环境中,你可以使用 prerender
将 React 树预渲染为静态 HTML:
1import { prerender } from 'react-dom/static'
2
3async function handler(request) {
4 const { prelude } = await prerender(<App />, {
5 bootstrapScripts: ['/main.js'],
6 })
7 return new Response(prelude, {
8 headers: { 'content-type': 'text/html' },
9 })
10}
prerender
API 会等待所有数据加载,然后再返回静态 HTML 流。
流可以转换为字符串,或者与流响应一起发送。它们在加载时不支持流内容,而现有的 React DOM 服务端渲染 API 支持流内容。
03. React 服务器组件
3.1 RSC(服务器组件)
RSC[2] 是一个新选项,允许在打包之前在与客户端应用或 SSR 服务器分开的环境中提前渲染组件。这个单独的环境是 RSC 中的“服务器”。
RSC 可以在构建时在 CI 服务器上运行一次,也可以使用 Web 服务器针对每个请求运行。
3.2 RSA(服务器动作)
RSA[3] 允许客户端组件调用在服务器上执行的异步函数。
当使用 "use server"
指令定义 RSA 时,你的框架会自动创建对服务器函数的引用,并将该引用传递给客户端组件。当客户端调用该函数时,React 将向服务器发送请求来执行该函数,并返回结果。
一个常见的误解是使用 "use server"
来表示 RSC,但其实没有针对 RSC 的指令。"use server"
指令只适用于 RSA。
RSA 可以在 RSC 中创建,并作为 props 传递给客户端组件,也可以在客户端组件中导入和使用。
高潮总结
React 19 是 React 18 时隔两年后发布的主版本升级,涉及的新功能较多,RSC 等部分技术细节需要大家进一步阅读文档。
参考文献
[1] React 官方博客: react.dev/blog/2024/1…
[2] RSC: react.dev/reference/r…
[3] RSA: react.dev/reference/r…