React v19

2024年12月05日 由 React 团队

🌐 December 05, 2024 by The React Team


注意

React 19 现在已稳定!

🌐 React 19 is now stable!

自从这篇文章最初在四月与 React 19 RC 一起分享以来的新增内容:

🌐 Additions since this post was originally shared with the React 19 RC in April:

此帖的日期已更新,以反映稳定版本的发布日期。

🌐 The date for this post has been updated to reflect the stable release date.

React v19 现在可以在 npm 上获取!

🌐 React v19 is now available on npm!

在我们的React 19 升级指南中,我们分享了将应用升级到 React 19 的逐步操作说明。在本文中,我们将概述 React 19 的新功能,以及你如何采用它们。

🌐 In our React 19 Upgrade Guide, we shared step-by-step instructions for upgrading your app to React 19. In this post, we’ll give an overview of the new features in React 19, and how you can adopt them.

有关重大变更的列表,请参阅 升级指南

🌐 For a list of breaking changes, see the Upgrade Guide.


React 19 有什么新内容

🌐 What’s new in React 19

动作

🌐 Actions

在 React 应用中,一个常见的用例是执行数据变更,然后根据响应更新状态。例如,当用户提交表单以更改他们的名称时,你将发出 API 请求,然后处理响应。过去,你需要手动处理等待状态、错误、乐观更新和顺序请求。

🌐 A common use case in React apps is to perform a data mutation and then update state in response. For example, when a user submits a form to change their name, you will make an API request, and then handle the response. In the past, you would need to handle pending states, errors, optimistic updates, and sequential requests manually.

例如,你可以在 useState 中处理挂起和错误状态:

🌐 For example, you could handle the pending and error state in 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 中,我们正在添加对在过渡中使用异步函数的支持,以自动处理挂起状态、错误、表单和乐观更新。

🌐 In React 19, we’re adding support for using async functions in transitions to handle pending states, errors, forms, and optimistic updates automatically.

例如,你可以使用 useTransition 来为你处理等待状态:

🌐 For example, you can use useTransition to handle the pending state for you:

// 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 的响应性和交互性。

🌐 The async transition will immediately set the isPending state to true, make the async request(s), and switch isPending to false after any transitions. This allows you to keep the current UI responsive and interactive while the data is changing.

注意

按照惯例,使用异步转换的函数被称为“动作”。

🌐 By convention, functions that use async transitions are called “Actions”.

操作会自动为你管理数据提交:

🌐 Actions automatically manage submitting data for you:

  • 待处理状态:操作提供一个待处理状态,该状态在请求开始时启动,并在最终状态更新提交时自动重置。
  • 乐观更新:操作支持新的 useOptimistic 钩子,因此在请求提交时,你可以向用户显示即时反馈。
  • 错误处理:操作提供错误处理功能,以便在请求失败时显示错误边界,并自动将乐观更新恢复到其原始值。
  • 表单<form> 元素现在支持将函数传递给 actionformAction 属性。将函数传递给 action 属性默认使用 Actions 并在提交后自动重置表单。

在 Actions 的基础上,React 19 引入了 useOptimistic 来管理乐观更新,以及一个新的钩子 React.useActionState 来处理 Actions 的常见情况。在 react-dom 中,我们添加了 <form> Actions 来自动管理表单,以及 useFormStatus 来支持表单中 Actions 的常见情况。

🌐 Building on top of Actions, React 19 introduces useOptimistic to manage optimistic updates, and a new hook React.useActionState to handle common cases for Actions. In react-dom we’re adding <form> Actions to manage forms automatically and useFormStatus to support the common cases for Actions in forms.

在 React 19 中,上面的示例可以简化为:

🌐 In React 19, the above example can be simplified to:

// 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 中的每个新操作功能。

🌐 In the next section, we’ll break down each of the new Action features in React 19.

新钩子:useActionState

🌐 New hook: useActionState

为了使 Actions 的常见情况更容易处理,我们添加了一个名为 useActionState 的新钩子:

🌐 To make the common cases easier for Actions, we’ve added a new hook called 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 接受一个函数(“Action”),并返回一个封装后的 Action 以供调用。这可行是因为 Actions 可以组合。当封装的 Action 被调用时,useActionState 会将 Action 的最后结果作为 data 返回,并将 Action 的挂起状态作为 pending 返回。

注意

React.useActionState 在 Canary 版本中之前被称为 ReactDOM.useFormState,但我们已经重命名它并弃用 useFormState

有关更多信息,请参见 #28491

🌐 See #28491 for more info.

有关更多信息,请参阅 useActionState 的文档。

🌐 For more information, see the docs for useActionState.

React DOM:<form> 操作

🌐 React DOM: <form> Actions

操作还与 React 19 的新 <form> 功能用于 react-dom 集成。我们已经增加了支持,将函数作为 <form><input><button> 元素的 actionformAction 属性传递,以便使用 Actions 自动提交表单:

🌐 Actions are also integrated with React 19’s new <form> features for react-dom. We’ve added support for passing functions as the action and formAction props of <form>, <input>, and <button> elements to automatically submit forms with Actions:

<form action={actionFunction}>

<form> 操作成功时,React 会自动重置非受控组件的表单。如果你需要手动重置 <form>,可以调用新的 requestFormReset React DOM API。

🌐 When a <form> Action succeeds, React will automatically reset the form for uncontrolled components. If you need to reset the <form> manually, you can call the new requestFormReset React DOM API.

有关更多信息,请参阅 <form><input><button>react-dom 文档。

🌐 For more information, see the react-dom docs for <form>, <input>, and <button>.

React DOM:新钩子:useFormStatus

🌐 React DOM: New hook: useFormStatus

在设计系统中,通常会编写需要访问其所在 <form> 信息的设计组件,而无需将 props 一层层传递给组件。这可以通过 Context 来实现,但为了让常见情况更简单,我们添加了一个新的钩子 useFormStatus

🌐 In design systems, it’s common to write design components that need access to information about the <form> they’re in, without drilling props down to the component. This can be done via Context, but to make the common case easier, we’ve added a new hook useFormStatus:

import {useFormStatus} from 'react-dom';

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

useFormStatus 读取父级 <form> 的状态,就好像该表单是一个上下文提供者一样。

欲了解更多信息,请参阅 useFormStatusreact-dom 文档。

🌐 For more information, see the react-dom docs for useFormStatus.

新钩子:useOptimistic

🌐 New hook: useOptimistic

在执行数据变更时,另一种常见的 UI 模式是,在异步请求进行中,乐观地显示最终状态。在 React 19 中,我们添加了一个名为 useOptimistic 的新钩子来简化这一过程:

🌐 Another common UI pattern when performing a data mutation is to show the final state optimistically while the async request is underway. In React 19, we’re adding a new hook called useOptimistic to make this easier:

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 值。

🌐 The useOptimistic hook will immediately render the optimisticName while the updateName request is in progress. When the update finishes or errors, React will automatically switch back to the currentName value.

有关更多信息,请参阅 useOptimistic 的文档。

🌐 For more information, see the docs for useOptimistic.

新 API:use

🌐 New API: use

在 React 19 中,我们引入了一个用于在渲染中读取资源的新 API:use

🌐 In React 19 we’re introducing a new API to read resources in render: use.

例如,你可以使用 use 读取一个 promise,React 会暂停直到该 promise 解决:

🌐 For example, you can read a promise with use, and React will Suspend until the promise resolves:

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 不支持在渲染中创建的 Promise。

🌐 use does not support promises created in render.

如果你尝试将渲染中创建的 promise 传递给 use,React 会发出警告:

🌐 If you try to pass a promise created in render to use, React will warn:

Console
A component was suspended by an uncached promise. Creating promises inside a Client Component or hook is not yet supported, except via a Suspense-compatible library or framework.

要修复,你需要传递一个来自支持 Suspense 的库或框架的承诺,该库或框架支持对承诺进行缓存。未来我们计划推出功能,使在渲染中缓存承诺更加容易。

🌐 To fix, you need to pass a promise from a Suspense powered library or framework that supports caching for promises. In the future we plan to ship features to make it easier to cache promises in render.

你也可以使用 use 来读取上下文,这允许你在某些条件下读取上下文,例如在早期返回之后:

🌐 You can also read context with use, allowing you to read Context conditionally such as after early returns:

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 只能在渲染中调用,类似于 hooks。与 hooks 不同,use 可以有条件地调用。未来我们计划通过 use 支持在渲染中以更多方式使用资源。

🌐 The use API can only be called in render, similar to hooks. Unlike hooks, use can be called conditionally. In the future we plan to support more ways to consume resources in render with use.

有关更多信息,请参阅 use 的文档。

🌐 For more information, see the docs for use.

新的 React DOM 静态 API

🌐 New React DOM Static APIs

我们为 react-dom/static 添加了两个新的用于静态网站生成的 API:

🌐 We’ve added two new APIs to react-dom/static for static site generation:

这些新的 API 通过在生成静态 HTML 时等待数据加载来改进 renderToString。它们设计用于与流式环境(如 Node.js Streams 和 Web Streams)一起使用。例如,在 Web Stream 环境中,你可以使用 prerender 将 React 树预渲染为静态 HTML:

🌐 These new APIs improve on renderToString by waiting for data to load for static HTML generation. They are designed to work with streaming environments like Node.js Streams and Web Streams. For example, in a Web Stream environment, you can prerender a React tree to static HTML with prerender:

import { prerender } from 'react-dom/static';

async function handler(request) {
const {prelude} = await prerender(<App />, {
bootstrapScripts: ['/main.js']
});
return new Response(prelude, {
headers: { 'content-type': 'text/html' },
});
}

预渲染 API 会在返回静态 HTML 流之前等待所有数据加载完成。流可以转换为字符串,或通过流式响应发送。它们不支持在内容加载时进行流式传输,而这是现有的 React DOM 服务器渲染 API 所支持的。

🌐 Prerender APIs will wait for all data to load before returning the static HTML stream. Streams can be converted to strings, or sent with a streaming response. They do not support streaming content as it loads, which is supported by the existing React DOM server rendering APIs.

欲了解更多信息,请参阅 React DOM 静态 API

🌐 For more information, see React DOM Static APIs.

React 服务器组件

🌐 React Server Components

服务器组件

🌐 Server Components

服务器组件是一种新选项,允许在打包之前提前渲染组件,在一个独立于客户端应用或 SSR 服务器的环境中。这个独立的环境就是 React 服务器组件中的“服务器”。服务器组件可以在 CI 服务器上构建时运行一次,或者通过网络服务器针对每个请求运行。

🌐 Server Components are a new option that allows rendering components ahead of time, before bundling, in an environment separate from your client application or SSR server. This separate environment is the “server” in React Server Components. Server Components can run once at build time on your CI server, or they can be run for each request using a web server.

React 19 包含了从 Canary 通道引入的所有 React 服务器组件功能。这意味着随服务器组件一起发布的库现在可以将 React 19 作为同级依赖目标,并使用 react-server 导出条件 在支持 全栈 React 架构 的框架中使用。

🌐 React 19 includes all of the React Server Components features included from the Canary channel. This means libraries that ship with Server Components can now target React 19 as a peer dependency with a react-server export condition for use in frameworks that support the Full-stack React Architecture.

注意

如何构建对服务器组件的支持?

🌐 How do I build support for Server Components?

虽然 React 19 中的 React Server Components 是稳定的,不会在小版本之间中断,但用于实现 React Server Components 打包器或框架的底层 API 不遵循 semver,可能会在 React 19.x 中的小版本之间中断。

🌐 While React Server Components in React 19 are stable and will not break between minor versions, the underlying APIs used to implement a React Server Components bundler or framework do not follow semver and may break between minors in React 19.x.

为了支持作为打包工具或框架的 React 服务器组件,我们建议固定到特定的 React 版本,或使用 Canary 版本。我们将继续与打包工具和框架合作,以在未来稳定用于实现 React 服务器组件的 API。

🌐 To support React Server Components as a bundler or framework, we recommend pinning to a specific React version, or using the Canary release. We will continue working with bundlers and frameworks to stabilize the APIs used to implement React Server Components in the future.

更多信息,请参见 React 服务器组件 的文档。

🌐 For more, see the docs for React Server Components.

服务器操作

🌐 Server Actions

服务器操作允许客户端组件调用在服务器上执行的异步函数。

🌐 Server Actions allow Client Components to call async functions executed on the server.

当使用 "use server" 指令定义服务器操作时,你的框架将自动创建对服务器函数的引用,并将该引用传递给客户端组件。当在客户端调用该函数时,React 将发送请求到服务器以执行该函数,并返回结果。

🌐 When a Server Action is defined with the "use server" directive, your framework will automatically create a reference to the server function, and pass that reference to the Client Component. When that function is called on the client, React will send a request to the server to execute the function, and return the result.

注意

没有用于服务器组件的指令。

🌐 There is no directive for Server Components.

一个常见的误解是认为服务器组件用 "use server" 标记,但实际上没有用于服务器组件的指令。"use server" 指令用于服务器操作。

🌐 A common misunderstanding is that Server Components are denoted by "use server", but there is no directive for Server Components. The "use server" directive is used for Server Actions.

更多信息,请参见 指令 的文档。

🌐 For more info, see the docs for Directives.

服务器操作可以在服务器组件中创建,并作为属性传递给客户端组件,或者可以在客户端组件中导入并使用。

🌐 Server Actions can be created in Server Components and passed as props to Client Components, or they can be imported and used in Client Components.

更多信息,请参见 React Server Actions 的文档。

🌐 For more, see the docs for React Server Actions.

React 19 的改进

🌐 Improvements in React 19

ref 作为属性

🌐 ref as a prop

从 React 19 开始,你现在可以将 ref 作为函数组件的属性访问:

🌐 Starting in React 19, you can now access ref as a prop for function components:

function MyInput({placeholder, ref}) {
return <input placeholder={placeholder} ref={ref} />
}

//...
<MyInput ref={ref} />

新的函数组件将不再需要 forwardRef,我们将发布一个代码修改工具,以自动更新你的组件使用新的 ref 属性。在未来的版本中,我们将弃用并移除 forwardRef

🌐 New function components will no longer need forwardRef, and we will be publishing a codemod to automatically update your components to use the new ref prop. In future versions we will deprecate and remove forwardRef.

注意

ref 传递给类时不会作为 props 传递,因为它们引用组件实例。

水化错误的差异

🌐 Diffs for hydration errors

我们还改进了 react-dom 中的水合错误报告。例如,不再在开发环境中记录多个错误却没有任何关于不匹配的信息:

🌐 We also improved error reporting for hydration errors in react-dom. For example, instead of logging multiple errors in DEV without any information about the mismatch:

Console
Warning: Text content did not match. Server: “Server” Client: “Client” at span at App
Warning: An error occurred during hydration. The server HTML was replaced with client content in <div>.
Warning: Text content did not match. Server: “Server” Client: “Client” at span at App
Warning: An error occurred during hydration. The server HTML was replaced with client content in <div>.
Uncaught Error: Text content does not match server-rendered HTML. at checkForUnmatchedText

我们现在记录一条带有不匹配差异的单条消息:

🌐 We now log a single message with a diff of the mismatch:

Console
Uncaught Error: Hydration failed because the server rendered HTML didn’t match the client. As a result this tree will be regenerated on the client. This can happen if an SSR-ed Client Component used: - A server/client branch if (typeof window !== 'undefined'). - Variable input such as Date.now() or Math.random() which changes each time it’s called. - Date formatting in a user’s locale which doesn’t match the server. - External changing data without sending a snapshot of it along with the HTML. - Invalid HTML tag nesting. It can also happen if the client has a browser extension installed which messes with the HTML before React loaded. https://react.dev/link/hydration-mismatch <App> <span> + Client - Server at throwOnHydrationMismatch

<Context> 作为提供者

🌐 <Context> as a provider

在 React 19 中,你可以将 <Context> 渲染为提供者,而不是 <Context.Provider>

🌐 In React 19, you can render <Context> as a provider instead of <Context.Provider>:

const ThemeContext = createContext('');

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

新的 Context 提供者可以使用 <Context>,我们将发布一个 codemod 来转换现有的提供者。在未来的版本中,我们将弃用 <Context.Provider>

🌐 New Context providers can use <Context> and we will be publishing a codemod to convert existing providers. In future versions we will deprecate <Context.Provider>.

refs 的清理函数

🌐 Cleanup functions for refs

我们现在支持从 ref 回调中返回清理函数:

🌐 We now support returning a cleanup function from ref callbacks:

<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 引用、类组件的引用以及 useImperativeHandle

🌐 When the component unmounts, React will call the cleanup function returned from the ref callback. This works for DOM refs, refs to class components, and useImperativeHandle.

注意

以前,React 在卸载组件时会使用 null 调用 ref 函数。如果你的 ref 返回一个清理函数,React 现在将跳过此步骤。

🌐 Previously, React would call ref functions with null when unmounting the component. If your ref returns a cleanup function, React will now skip this step.

在未来的版本中,我们将弃用在卸载组件时使用 null 调用 refs。

🌐 In future versions, we will deprecate calling refs with null when unmounting components.

由于引入了引用清理函数,从 ref 回调中返回其他任何内容现在都会被 TypeScript 拒绝。通常的解决方法是停止使用隐式返回,例如:

🌐 Due to the introduction of ref cleanup functions, returning anything else from a ref callback will now be rejected by TypeScript. The fix is usually to stop using implicit returns, for example:

- <div ref={current => (instance = current)} />
+ <div ref={current => {instance = current}} />

原始代码返回了 HTMLDivElement 的实例,而 TypeScript 不知道这是否 应该 是一个清理函数,或者你根本不想返回一个清理函数。

🌐 The original code returned the instance of the HTMLDivElement and TypeScript wouldn’t know if this was supposed to be a cleanup function or if you didn’t want to return a cleanup function.

你可以用 no-implicit-ref-callback-return 对这个模式进行代码迁移。

🌐 You can codemod this pattern with no-implicit-ref-callback-return.

useDeferredValue 初始值

🌐 useDeferredValue initial value

我们在 useDeferredValue 中添加了一个 initialValue 选项:

🌐 We’ve added an initialValue option to 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

🌐 For more, see useDeferredValue.

支持文档元数据

🌐 Support for Document Metadata

在 HTML 中,文档元数据标签如 <title><link><meta> 被保留用于放置在文档的 <head> 部分。在 React 中,决定应用适当元数据的组件可能距离渲染 <head> 的位置非常远,或者 React 根本不渲染 <head>。过去,这些元素需要在效果中手动插入,或者通过像 react-helmet 这样的库插入,并且在服务端渲染 React 应用时需要小心处理。

🌐 In HTML, document metadata tags like <title>, <link>, and <meta> are reserved for placement in the <head> section of the document. In React, the component that decides what metadata is appropriate for the app may be very far from the place where you render the <head> or React does not render the <head> at all. In the past, these elements would need to be inserted manually in an effect, or by libraries like react-helmet, and required careful handling when server rendering a React application.

在 React 19 中,我们正在添加对在组件中本地渲染文档元数据标签的支持:

🌐 In React 19, we’re adding support for rendering document metadata tags in components natively:

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 和服务器组件中都能正常工作。

🌐 When React renders this component, it will see the <title> <link> and <meta> tags, and automatically hoist them to the <head> section of document. By supporting these metadata tags natively, we’re able to ensure they work with client-only apps, streaming SSR, and Server Components.

注意

你可能仍然需要一个元数据库

🌐 You may still want a Metadata library

对于简单的用例,将文档元数据呈现为标签可能是合适的,但库可以提供更强大的功能,例如根据当前路由使用特定元数据覆盖通用元数据。这些功能使框架和库如 react-helmet 更容易支持元数据标签,而不是取代它们。

🌐 For simple use cases, rendering Document Metadata as tags may be suitable, but libraries can offer more powerful features like overriding generic metadata with specific metadata based on the current route. These features make it easier for frameworks and libraries like react-helmet to support metadata tags, rather than replace them.

欲了解更多信息,请参阅 <title><link><meta> 的文档。

🌐 For more info, see the docs for <title>, <link>, and <meta>.

对样式表的支持

🌐 Support for stylesheets

样式表,无论是外部链接的(<link rel="stylesheet" href="...">)还是内联的(<style>...</style>),由于样式优先级规则,都需要在 DOM 中仔细定位。构建一个允许在组件内进行组合的样式表功能是困难的,因此用户往往最终要么将所有样式加载到可能依赖它们的组件较远的位置,要么使用封装了这种复杂性的样式库。

🌐 Stylesheets, both externally linked (<link rel="stylesheet" href="...">) and inline (<style>...</style>), require careful positioning in the DOM due to style precedence rules. Building a stylesheet capability that allows for composability within components is hard, so users often end up either loading all of their styles far from the components that may depend on them, or they use a style library which encapsulates this complexity.

在 React 19 中,我们正在解决这种复杂性,并提供对客户端并发渲染和服务器端流式渲染的更深层集成,同时内置支持样式表。如果你告诉 React 你的样式表的 precedence,它将管理样式表在 DOM 中的插入顺序,并确保样式表(如果是外部的)在显示依赖这些样式规则的内容之前加载完成。

🌐 In React 19, we’re addressing this complexity and providing even deeper integration into Concurrent Rendering on the Client and Streaming Rendering on the Server with built in support for stylesheets. If you tell React the precedence of your stylesheet it will manage the insertion order of the stylesheet in the DOM and ensure that the stylesheet (if external) is loaded before revealing content that depends on those style rules.

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> 中。

🌐 During Server Side Rendering React will include the stylesheet in the <head>, which ensures that the browser will not paint until it has loaded. If the stylesheet is discovered late after we’ve already started streaming, React will ensure that the stylesheet is inserted into the <head> on the client before revealing the content of a Suspense boundary that depends on that stylesheet.

在客户端渲染期间,React 会等待新渲染的样式表加载完成后再提交渲染。如果你在应用的多个地方渲染此组件,React 只会在文档中包含一次样式表:

🌐 During Client Side Rendering React will wait for newly rendered stylesheets to load before committing the render. If you render this component from multiple places within your application React will only include the stylesheet once in the document:

function App() {
return <>
<ComponentOne />
...
<ComponentOne /> // won't lead to a duplicate stylesheet link in the DOM
</>
}

对于习惯手动加载样式表的用户来说,这是一个将这些样式表与依赖它们的组件放在一起的机会,从而可以更好地进行本地推断,并更容易确保只加载实际依赖的样式表。

🌐 For users accustomed to loading stylesheets manually this is an opportunity to locate those stylesheets alongside the components that depend on them allowing for better local reasoning and an easier time ensuring you only load the stylesheets that you actually depend on.

样式库和与打包工具的样式集成也可以采用这一新功能,因此即使你不直接渲染自己的样式表,也可以在你的工具升级以使用此功能时受益。

🌐 Style libraries and style integrations with bundlers can also adopt this new capability so even if you don’t directly render your own stylesheets, you can still benefit as your tools are upgraded to use this feature.

欲了解更多详情,请阅读 <link><style> 的文档。

🌐 For more details, read the docs for <link> and <style>.

支持异步脚本

🌐 Support for async scripts

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

🌐 In HTML normal scripts (<script src="...">) and deferred scripts (<script defer="" src="...">) load in document order which makes rendering these kinds of scripts deep within your component tree challenging. Async scripts (<script async="" src="...">) however will load in arbitrary order.

在 React 19 中,我们通过允许你在组件树中的任何位置渲染异步脚本(在实际依赖该脚本的组件内部),提供了对异步脚本的更好支持,而无需管理脚本实例的重新定位和去重。

🌐 In React 19 we’ve included better support for async scripts by allowing you to render them anywhere in your component tree, inside the components that actually depend on the script, without having to manage relocating and deduplicating script instances.

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 也只会加载和执行该脚本一次。

🌐 In all rendering environments, async scripts will be deduplicated so that React will only load and execute the script once even if it is rendered by multiple different components.

在服务器端渲染中,异步脚本将被包含在 <head> 中,并且其优先级低于阻塞渲染的更关键资源,例如样式表、字体和图片预加载。

🌐 In Server Side Rendering, async scripts will be included in the <head> and prioritized behind more critical resources that block paint such as stylesheets, fonts, and image preloads.

有关更多详情,请阅读 <script> 的文档。

🌐 For more details, read the docs for <script>.

支持预加载资源

🌐 Support for preloading resources

在初始文档加载和客户端更新期间,尽早告诉浏览器它可能需要加载的资源,可能会对页面性能产生显著影响。

🌐 During initial document load and on client side updates, telling the Browser about resources that it will likely need to load as early as possible can have a dramatic effect on page performance.

React 19 包含了许多用于加载和预加载浏览器资源的新 API,使构建不会因资源加载效率低下而受限的出色体验变得尽可能简单。

🌐 React 19 includes a number of new APIs for loading and preloading Browser resources to make it as easy as possible to build great experiences that aren’t held back by inefficient resource loading.

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 可以通过将字体等附加资源的发现从样式表加载中移出,从而优化初始页面加载。它们还可以通过预取预期导航中使用的资源列表,然后在点击甚至悬停时主动预加载这些资源,从而加快客户端更新速度。

🌐 These APIs can be used to optimize initial page loads by moving discovery of additional resources like fonts out of stylesheet loading. They can also make client updates faster by prefetching a list of resources used by an anticipated navigation and then eagerly preloading those resources on click or even on hover.

有关更多详细信息,请参见 资源预加载 API

🌐 For more details see Resource Preloading APIs.

与第三方脚本和扩展的兼容性

🌐 Compatibility with third-party scripts and extensions

我们改进了水合处理,以适应第三方脚本和浏览器扩展。

🌐 We’ve improved hydration to account for third-party scripts and browser extensions.

在进行 hydration 时,如果在客户端渲染的元素与服务器返回的 HTML 中的元素不匹配,React 将强制进行客户端重新渲染以修复内容。以前,如果元素是由第三方脚本或浏览器扩展插入的,它会触发不匹配错误并进行客户端渲染。

🌐 When hydrating, if an element that renders on the client doesn’t match the element found in the HTML from the server, React will force a client re-render to fix up the content. Previously, if an element was inserted by third-party scripts or browser extensions, it would trigger a mismatch error and client render.

在 React 19 中,<head><body> 中的意外标签将被跳过,从而避免不匹配错误。如果由于无关的 hydration 不匹配而需要重新渲染整个文档,React 将保留第三方脚本和浏览器扩展插入的样式表。

🌐 In React 19, unexpected tags in the <head> and <body> will be skipped over, avoiding the mismatch errors. If React needs to re-render the entire document due to an unrelated hydration mismatch, it will leave in place stylesheets inserted by third-party scripts and browser extensions.

更好的错误报告

🌐 Better error reporting

我们在 React 19 中改进了错误处理,以消除重复并提供处理已捕获和未捕获错误的选项。例如,当渲染中出现被错误边界捕获的错误时,以前 React 会抛出两次错误(一次为原始错误,另一次是在无法自动恢复后再次抛出),然后调用 console.error 提供有关错误发生位置的信息。

🌐 We improved error handling in React 19 to remove duplication and provide options for handling caught and uncaught errors. For example, when there’s an error in render caught by an Error Boundary, previously React would throw the error twice (once for the original error, then again after failing to automatically recover), and then call console.error with info about where the error occurred.

这导致每捕获一个错误就出现三个错误:

🌐 This resulted in three errors for every caught error:

Console
Uncaught Error: hit at Throws at renderWithHooks
Uncaught Error: hit <-- Duplicate at Throws at renderWithHooks
The above error occurred in the Throws component: at Throws at ErrorBoundary at App React will try to recreate this component tree from scratch using the error boundary you provided, ErrorBoundary.

在 React 19 中,我们记录一个包含所有错误信息的单个错误:

🌐 In React 19, we log a single error with all the error information included:

Console
Error: hit at Throws at renderWithHooks The above error occurred in the Throws component: at Throws at ErrorBoundary at App React will try to recreate this component tree from scratch using the error boundary you provided, ErrorBoundary. at ErrorBoundary at App

此外,我们添加了两个新的根选项来补充 onRecoverableError

🌐 Additionally, we’ve added two new root options to complement onRecoverableError:

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

有关更多信息和示例,请参见 createRoothydrateRoot 的文档。

🌐 For more info and examples, see the docs for createRoot and hydrateRoot.

对自定义元素的支持

🌐 Support for Custom Elements

React 19 增加了对自定义元素的全面支持,并通过了 Custom Elements Everywhere 上的所有测试。

🌐 React 19 adds full support for custom elements and passes all tests on Custom Elements Everywhere.

在早期版本中,在 React 中使用自定义元素一直很困难,因为 React 会将未识别的属性视为 HTML 属性而不是组件属性。在 React 19 中,我们增加了对属性的支持,这种支持在客户端和服务端渲染(SSR)中都适用,具体策略如下:

🌐 In past versions, using Custom Elements in React has been difficult because React treated unrecognized props as attributes rather than properties. In React 19, we’ve added support for properties that works on the client and during SSR with the following strategy:

  • 服务器端渲染:传递给自定义元素的 props 如果是原始类型的值(如 stringnumber)或值为 true,将会渲染为属性。非原始类型的 props(如 objectsymbolfunction)或值为 false 的 prop 将被省略。
  • 客户端渲染:与自定义元素实例上的属性匹配的 props 将被分配为属性,否则它们将被分配为特性。

感谢 Joey Arhar 推动了 React 中自定义元素支持的设计和实现。

🌐 Thanks to Joey Arhar for driving the design and implementation of Custom Element support in React.

如何升级

🌐 How to upgrade

请参阅 React 19 升级指南 获取分步说明以及所有重大和显著更改的完整列表。

🌐 See the React 19 Upgrade Guide for step-by-step instructions and a full list of breaking and notable changes.

注意:此文章最初发表于2024年4月25日,并已更新至2024年12月5日的稳定版本。

🌐 Note: this post was originally published 04/25/2024 and has been updated to 12/05/2024 with the stable release.