React 19 RC
2024 年 4 月 25 日 由 React 团队
React 19 RC 现已在 npm 上发布!
在我们的 React 19 RC 升级指南 中,我们分享了将您的应用程序升级到 React 19 的分步说明。在本文中,我们将概述 React 19 中的新功能以及如何采用它们。
有关重大更改的列表,请参阅 升级指南。
React 19 中的新功能
操作
React 应用程序中一个常见的用例是执行数据变异,然后相应地更新状态。例如,当用户提交表单更改其姓名时,您将发出 API 请求,然后处理响应。过去,您需要手动处理挂起状态、错误、乐观更新和顺序请求。
例如,您可以在 useState
中处理挂起状态和错误状态
// Before Actions
function UpdateName({}) {
const [name, setName] = useState("");
const [error, setError] = useState(null);
const [isPending, setIsPending] = useState(false);
const handleSubmit = async () => {
setIsPending(true);
const error = await updateName(name);
setIsPending(false);
if (error) {
setError(error);
return;
}
redirect("/path");
};
return (
<div>
<input value={name} onChange={(event) => setName(event.target.value)} />
<button onClick={handleSubmit} disabled={isPending}>
Update
</button>
{error && <p>{error}</p>}
</div>
);
}
在 React 19 中,我们添加了对在过渡中使用异步函数的支持,以自动处理挂起状态、错误、表单和乐观更新。
例如,您可以使用 useTransition
来为您处理挂起状态
// Using pending state from Actions
function UpdateName({}) {
const [name, setName] = useState("");
const [error, setError] = useState(null);
const [isPending, startTransition] = useTransition();
const handleSubmit = () => {
startTransition(async () => {
const error = await updateName(name);
if (error) {
setError(error);
return;
}
redirect("/path");
})
};
return (
<div>
<input value={name} onChange={(event) => setName(event.target.value)} />
<button onClick={handleSubmit} disabled={isPending}>
Update
</button>
{error && <p>{error}</p>}
</div>
);
}
异步过渡将立即将 isPending
状态设置为 true,发出异步请求,并在任何过渡后将 isPending
切换为 false。这使您可以在数据更改时保持当前 UI 的响应性和交互性。
在操作的基础上,React 19 引入了 useOptimistic
来管理乐观更新,以及一个新的挂钩 React.useActionState
来处理操作的常见情况。在 react-dom
中,我们添加了 <form>
操作 以自动管理表单,以及 useFormStatus
以支持表单中操作的常见情况。
在 React 19 中,上面的示例可以简化为
// Using <form> Actions and useActionState
function ChangeName({ name, setName }) {
const [error, submitAction, isPending] = useActionState(
async (previousState, formData) => {
const error = await updateName(formData.get("name"));
if (error) {
return error;
}
redirect("/path");
return null;
},
null,
);
return (
<form action={submitAction}>
<input type="text" name="name" />
<button type="submit" disabled={isPending}>Update</button>
{error && <p>{error}</p>}
</form>
);
}
在下一节中,我们将分解 React 19 中每个新的操作功能。
新挂钩:useActionState
为了使操作的常见情况更容易,我们添加了一个名为 useActionState
的新挂钩
const [error, submitAction, isPending] = useActionState(
async (previousState, newName) => {
const error = await updateName(newName);
if (error) {
// You can return any result of the action.
// Here, we return only the error.
return error;
}
// handle success
return null;
},
null,
);
useActionState
接受一个函数(“操作”),并返回一个包装后的操作以供调用。这是因为操作可以组合。当包装后的操作被调用时,useActionState
将返回操作的最后结果作为 data
,并将操作的挂起状态作为 pending
。
有关更多信息,请参阅 useActionState
的文档。
React DOM:<form>
操作
操作也与 React 19 的新<form>
功能集成到 react-dom
中。我们添加了对将函数作为action
和 formAction
传递给<form>
、<input>
和 <button>
元素的支持,以便使用操作自动提交表单。
<form action={actionFunction}>
当 <form>
操作成功时,React 将自动重置未受控组件的表单。如果您需要手动重置<form>
,您可以调用新的 requestFormReset
React DOM API。
有关更多信息,请参阅 react-dom
文档,了解 <form>
、<input>
和 <button>
。
React DOM:新钩子:useFormStatus
在设计系统中,通常会编写需要访问有关其所在<form>
信息的设计组件,而无需将属性向下传递到组件。这可以通过 Context 完成,但为了使常见情况更轻松,我们添加了一个新的钩子 useFormStatus
import {useFormStatus} from 'react-dom';
function DesignButton() {
const {pending} = useFormStatus();
return <button type="submit" disabled={pending} />
}
useFormStatus
读取父 <form>
的状态,就像该表单是 Context 提供者一样。
有关更多信息,请参阅 react-dom
文档,了解 useFormStatus
.
新钩子:useOptimistic
执行数据变异时,另一个常见的 UI 模式是在异步请求正在进行时乐观地显示最终状态。在 React 19 中,我们添加了一个名为useOptimistic
的新钩子,使这更容易。
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>
);
}
useOptimistic
钩子将在updateName
请求正在进行时立即呈现 optimisticName
。当更新完成或发生错误时,React 将自动切换回currentName
值。
有关更多信息,请参阅 useOptimistic
文档。
新 API:use
在 React 19 中,我们引入了新的 API 来在渲染中读取资源:use
。
例如,您可以使用 use
读取 Promise,React 将挂起直到 Promise 解析。
import {use} from 'react';
function Comments({commentsPromise}) {
// `use` will suspend until the promise resolves.
const comments = use(commentsPromise);
return comments.map(comment => <p key={comment.id}>{comment}</p>);
}
function Page({commentsPromise}) {
// When `use` suspends in Comments,
// this Suspense boundary will be shown.
return (
<Suspense fallback={<div>Loading...</div>}>
<Comments commentsPromise={commentsPromise} />
</Suspense>
)
}
您也可以使用 use
读取 Context,允许您有条件地读取 Context,例如在早期返回之后。
import {use} from 'react';
import ThemeContext from './ThemeContext'
function Heading({children}) {
if (children == null) {
return null;
}
// This would not work with useContext
// because of the early return.
const theme = use(ThemeContext);
return (
<h1 style={{color: theme.color}}>
{children}
</h1>
);
}
use
API 只能在渲染中调用,类似于钩子。与钩子不同,use
可以有条件地调用。将来,我们计划支持更多使用 use
在渲染中使用资源的方法。
有关更多信息,请参阅 use
文档。
React 服务器组件
服务器组件
服务器组件是一种新的选项,允许您在打包之前,在与客户端应用程序或 SSR 服务器分离的环境中预先渲染组件。这个独立的环境就是 React 服务器组件中的“服务器”。服务器组件可以在构建时在您的 CI 服务器上运行一次,或者可以使用 Web 服务器为每个请求运行。
React 19 包括从 Canary 频道中包含的所有 React 服务器组件功能。这意味着与服务器组件一起发布的库现在可以将 React 19 作为对等依赖项,并使用 react-server
导出条件 用于在支持 全栈 React 架构 的框架中使用。
有关更多信息,请参阅 React 服务器组件 文档。
服务器操作
服务器操作允许客户端组件调用在服务器上执行的异步函数。
当使用 "use server"
指令定义服务器操作时,您的框架将自动创建对服务器函数的引用,并将该引用传递给客户端组件。当该函数在客户端被调用时,React 将向服务器发送请求以执行该函数,并返回结果。
服务器操作可以在服务器组件中创建,并作为道具传递给客户端组件,也可以在客户端组件中导入和使用。
有关更多信息,请参阅 React 服务器操作 文档。
React 19 中的改进
ref
作为道具
从 React 19 开始,您现在可以访问 ref
作为函数组件的道具
function MyInput({placeholder, ref}) {
return <input placeholder={placeholder} ref={ref} />
}
//...
<MyInput ref={ref} />
新的函数组件将不再需要 forwardRef
,我们将发布一个代码修改,以自动更新您的组件以使用新的 ref
道具。在未来的版本中,我们将弃用并删除 forwardRef
。
水合错误的差异
我们还改进了 react-dom
中水合错误的错误报告。例如,在 DEV 中,不是记录多个错误,没有任何关于不匹配的信息
我们现在记录一条包含不匹配差异的单个消息
if (typeof window !== 'undefined')
。- 每次调用时都会更改的变量输入,例如 Date.now()
或 Math.random()
。- 与服务器不匹配的用户所在地区的日期格式。- 在没有发送快照的情况下更改外部数据。- 无效的 HTML 标签嵌套。
如果客户端安装了会干扰 HTML 加载的浏览器扩展,也可能发生这种情况。
https://reactjs.ac.cn/link/hydration-mismatch
<App> <span>+ 客户端- 服务器
在 throwOnHydrationMismatch …<Context>
作为提供者
在 React 19 中,你可以将 <Context>
渲染为提供者,而不是 <Context.Provider>
。
const ThemeContext = createContext('');
function App({children}) {
return (
<ThemeContext value="dark">
{children}
</ThemeContext>
);
}
新的 Context 提供者可以使用 <Context>
,我们将会发布一个代码转换工具来转换现有的提供者。在未来的版本中,我们将弃用 <Context.Provider>
。
Ref 的清理函数
我们现在支持从 ref
回调函数中返回清理函数。
<input
ref={(ref) => {
// ref created
// NEW: return a cleanup function to reset
// the ref when element is removed from DOM.
return () => {
// ref cleanup
};
}}
/>
当组件卸载时,React 将调用从 ref
回调函数返回的清理函数。这适用于 DOM ref、指向类组件的 ref 和 useImperativeHandle
。
由于引入了 ref 清理函数,现在从 ref
回调函数中返回任何其他内容将被 TypeScript 拒绝。修复通常是停止使用隐式返回,例如
- <div ref={current => (instance = current)} />
+ <div ref={current => {instance = current}} />
原始代码返回了 HTMLDivElement
的实例,而 TypeScript 无法知道这是否应该是一个清理函数,或者你是否不想返回一个清理函数。
你可以使用 no-implicit-ref-callback-return
代码转换工具来转换这种模式。
useDeferredValue
初始值
我们已经添加了一个 initialValue
选项到 useDeferredValue
。
function Search({deferredValue}) {
// On initial render the value is ''.
// Then a re-render is scheduled with the deferredValue.
const value = useDeferredValue(deferredValue, '');
return (
<Results query={value} />
);
}
当提供 initialValue 时,useDeferredValue
将在组件的初始渲染中将其作为 value
返回,并在后台安排一个重新渲染,使用返回的 deferredValue。
更多信息,请查看 useDeferredValue
的文档。
支持文档元数据
在 HTML 中,文档元数据标签,例如 <title>
、<link>
和 <meta>
,被保留用于放置在文档的 <head>
部分。在 React 中,决定哪些元数据适合应用程序的组件可能离你渲染 <head>
的地方很远,或者 React 根本不渲染 <head>
。过去,这些元素需要在效果中手动插入,或者通过像 react-helmet
这样的库来插入,在服务器端渲染 React 应用程序时需要小心处理。
在 React 19 中,我们正在添加对在组件中本地渲染文档元数据标签的支持。
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>
);
}
当 React 渲染这个组件时,它会看到 <title>
、<link>
和 <meta>
标签,并自动将它们提升到文档的 <head>
部分。通过本地支持这些元数据标签,我们能够确保它们与仅客户端应用程序、流式 SSR 和服务器组件一起工作。
更多信息,请查看 <title>
、<link>
和 <meta>
的文档。
支持样式表
样式表,无论是外部链接的 (<link rel="stylesheet" href="...">
) 还是内联的 (<style>...</style>
),由于样式优先级规则,需要在 DOM 中进行小心定位。构建一个允许在组件中进行组合的样式表功能很困难,因此用户往往最终要么在远离可能依赖它们的组件的地方加载所有样式,要么使用一个封装了这种复杂性的样式库。
在 React 19 中,我们解决了这种复杂性,并提供了更深入的集成到客户端的并发渲染和服务器端的流式渲染中,内置了对样式表的支持。如果你告诉 React 你的样式表的优先级
,它将管理样式表在 DOM 中的插入顺序,并确保样式表(如果为外部样式表)在显示依赖于这些样式规则的内容之前加载。
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>
<link rel="stylesheet" href="baz" precedence="default" /> <-- will be inserted between foo & bar
</div>
)
}
在服务器端渲染期间,React 将在<head>
中包含样式表,这将确保浏览器在加载样式表之前不会进行绘制。如果样式表在开始流式传输后被发现较晚,React 将确保在显示依赖于该样式表的 Suspense 边界的内容之前,将样式表插入到客户端的<head>
中。
在客户端渲染期间,React 将等待新渲染的样式表加载完成,然后才提交渲染。如果你在应用程序中的多个位置渲染此组件,React 仅将样式表包含在文档中一次。
function App() {
return <>
<ComponentOne />
...
<ComponentOne /> // won't lead to a duplicate stylesheet link in the DOM
</>
}
对于习惯于手动加载样式表的用户来说,这是一个机会,可以将这些样式表与依赖它们的组件放在一起,从而实现更好的局部推理,并更容易确保仅加载实际依赖的样式表。
样式库和与捆绑器的样式集成也可以采用这种新功能,因此即使你没有直接渲染自己的样式表,你仍然可以从升级工具以使用此功能中受益。
有关更多详细信息,请阅读 <link>
和 <style>
的文档。
对异步脚本的支持
在 HTML 中,普通脚本 (<script src="...">
) 和延迟脚本 (<script defer="" src="...">
) 按文档顺序加载,这使得在组件树深处渲染这些类型的脚本具有挑战性。然而,异步脚本 (<script async="" src="...">
) 将按任意顺序加载。
在 React 19 中,我们通过允许你在组件树中的任何地方渲染异步脚本,在实际依赖脚本的组件内部渲染,而无需管理重新定位和对脚本实例进行重复数据删除,从而为异步脚本提供了更好的支持。
function MyComponent() {
return (
<div>
<script async={true} src="..." />
Hello World
</div>
)
}
function App() {
<html>
<body>
<MyComponent>
...
<MyComponent> // won't lead to duplicate script in the DOM
</body>
</html>
}
在所有渲染环境中,异步脚本都将进行重复数据删除,因此即使多个不同的组件渲染了该脚本,React 也只加载和执行该脚本一次。
在服务器端渲染中,异步脚本将被包含在<head>
中,并优先于阻止绘制的更关键资源,例如样式表、字体和图像预加载。
有关更多详细信息,请阅读 <script>
的文档。
对预加载资源的支持
在初始文档加载和客户端更新期间,尽早告诉浏览器它可能需要加载的资源,可以对页面性能产生重大影响。
React 19 包含许多新的 API 用于加载和预加载浏览器资源,使构建出色的体验变得尽可能容易,这些体验不会因资源加载效率低下而受到阻碍。
import { prefetchDNS, preconnect, preload, preinit } from 'react-dom'
function MyComponent() {
preinit('https://.../path/to/some/script.js', {as: 'script' }) // loads and executes this script eagerly
preload('https://.../path/to/font.woff', { as: 'font' }) // preloads this font
preload('https://.../path/to/stylesheet.css', { as: 'style' }) // preloads this stylesheet
prefetchDNS('https://...') // when you may not actually request anything from this host
preconnect('https://...') // when you will request something but aren't sure what
}
<!-- the above would result in the following DOM/HTML -->
<html>
<head>
<!-- links/scripts are prioritized by their utility to early loading, not call order -->
<link rel="prefetch-dns" href="https://...">
<link rel="preconnect" href="https://...">
<link rel="preload" as="font" href="https://.../path/to/font.woff">
<link rel="preload" as="style" href="https://.../path/to/stylesheet.css">
<script async="" src="https://.../path/to/some/script.js"></script>
</head>
<body>
...
</body>
</html>
这些 API 可用于优化初始页面加载,将额外资源(如字体)的发现从样式表加载中移出。它们还可以通过预取预期导航使用的资源列表,然后在点击甚至悬停时提前预加载这些资源,从而使客户端更新更快。
有关更多详细信息,请参阅 资源预加载 API。
与第三方脚本和扩展的兼容性
我们改进了水合作用,以考虑第三方脚本和浏览器扩展。
在水合期间,如果在客户端渲染的元素与服务器 HTML 中找到的元素不匹配,React 将强制执行客户端重新渲染以修复内容。以前,如果第三方脚本或浏览器扩展插入了元素,则会触发不匹配错误和客户端渲染。
在 React 19 中,<head>
和 <body>
中的意外标签将被跳过,避免出现不匹配错误。如果 React 需要由于不相关的水合作用不匹配而重新渲染整个文档,它将保留第三方脚本和浏览器扩展插入的样式表。
更好的错误报告
我们改进了 React 19 中的错误处理,以消除重复并提供处理已捕获和未捕获错误的选项。例如,当错误边界捕获渲染中的错误时,以前 React 会抛出两次错误(一次是原始错误,然后是在无法自动恢复后再次抛出),然后使用有关错误发生位置的信息调用console.error
。
这导致每个捕获的错误都会出现三个错误。
在 React 19 中,我们记录单个错误,其中包含所有错误信息。
此外,我们添加了两个新的根选项来补充onRecoverableError
onCaughtError
:当 React 在错误边界中捕获错误时调用。onUncaughtError
:当抛出错误且未被错误边界捕获时调用。onRecoverableError
:当抛出错误并自动恢复时调用。
有关更多信息和示例,请参阅 createRoot
和 hydrateRoot
的文档。
对自定义元素的支持
React 19 为自定义元素添加了完全支持,并在 自定义元素无处不在 中通过了所有测试。
在过去的版本中,在 React 中使用自定义元素一直很困难,因为 React 将无法识别的 props 视为属性而不是属性。在 React 19 中,我们添加了对属性的支持,该属性在客户端和 SSR 中使用以下策略工作
- 服务器端渲染:传递给自定义元素的 props 将作为属性呈现,如果它们的类型是原始值,例如
string
、number
或值为true
。具有非原始类型的 props,例如object
、symbol
、function
或值为false
将被省略。 - 客户端渲染:与自定义元素实例上的属性匹配的 props 将被分配为属性,否则将被分配为属性。
感谢 Joey Arhar 推动 React 中自定义元素支持的设计和实施。
如何升级
查看 React 19 升级指南 以获取分步说明和完整的重大更改和注意事项列表。