React 19.2

2025年10月1日 由 React 团队

🌐 October 1, 2025 by The React Team


React 19.2 现在可以在 npm 上使用!

🌐 React 19.2 is now available on npm!

这是我们在过去一年中的第三次发布,继去年12月的 React 19 和今年6月的 React 19.1 之后。在这篇文章中,我们将概述 React 19.2 中的新功能,并突出一些显著的变化。

🌐 This is our third release in the last year, following React 19 in December and React 19.1 in June. In this post, we’ll give an overview of the new features in React 19.2, and highlight some notable changes.


新的 React 功能

🌐 New React Features

<Activity />

<Activity> 让你将应用拆分成可以被控制和优先处理的“活动”。

你可以使用 Activity 作为有条件渲染应用部分的替代方法:

🌐 You can use Activity as an alternative to conditionally rendering parts of your app:

// Before
{isVisible && <Page />}

// After
<Activity mode={isVisible ? 'visible' : 'hidden'}>
<Page />
</Activity>

在 React 19.2 中,Activity 支持两种模式:visiblehidden

🌐 In React 19.2, Activity supports two modes: visible and hidden.

  • hidden:隐藏子元素,卸载副作用,并将所有更新延迟,直到 React 没有剩余工作可做。
  • visible:显示子级,挂载效果,并允许更新正常处理。

这意味着你可以预渲染并继续渲染应用中隐藏的部分,而不会影响屏幕上任何可见内容的性能。

🌐 This means you can pre-render and keep rendering hidden parts of the app without impacting the performance of anything visible on screen.

你可以使用 Activity 来呈现用户可能接下来会导航到的应用隐藏部分,或保存用户离开时部分的状态。这有助于通过在后台加载数据、CSS 和图片来加快导航速度,并允许返回导航时保持状态,例如输入字段。

🌐 You can use Activity to render hidden parts of the app that a user is likely to navigate to next, or to save the state of parts the user navigates away from. This helps make navigations quicker by loading data, css, and images in the background, and allows back navigations to maintain state such as input fields.

未来,我们计划为活动添加更多模式以适应不同的使用场景。

🌐 In the future, we plan to add more modes to Activity for different use cases.

有关如何使用 Activity 的示例,请查看 Activity 文档

🌐 For examples on how to use Activity, check out the Activity docs.


useEffectEvent

useEffect 的一个常见模式是通知应用代码关于来自外部系统的某种“事件”。例如,当聊天室连接时,你可能想要显示一个通知:

🌐 One common pattern with useEffect is to notify the app code about some kind of “events” from an external system. For example, when a chat room gets connected, you might want to display a notification:

function ChatRoom({ roomId, theme }) {
useEffect(() => {
const connection = createConnection(serverUrl, roomId);
connection.on('connected', () => {
showNotification('Connected!', theme);
});
connection.connect();
return () => {
connection.disconnect()
};
}, [roomId, theme]);
// ...

上面代码的问题在于,对在这种“事件”内部使用的任何值进行更改都会导致周围的 Effect 重新运行。例如,更改 theme 会导致聊天室重新连接。对于与 Effect 逻辑本身相关的值,比如 roomId,这是合理的,但对于 theme,则没有意义。

🌐 The problem with the code above is that a change to any values used inside such an “event” will cause the surrounding Effect to re-run. For example, changing the theme will cause the chat room to reconnect. This makes sense for values related to the Effect logic itself, like roomId, but it doesn’t make sense for theme.

为了解决这个问题,大多数用户只是禁用 lint 规则并排除该依赖。但这可能会导致错误,因为如果你以后需要更新 Effect,linter 就无法帮助你保持依赖的最新状态。

🌐 To solve this, most users just disable the lint rule and exclude the dependency. But that can lead to bugs since the linter can no longer help you keep the dependencies up to date if you need to update the Effect later.

使用 useEffectEvent,你可以将此逻辑中触发事件的 Effect 的“事件”部分分离出来:

🌐 With useEffectEvent, you can split the “event” part of this logic out of the Effect that emits it:

function ChatRoom({ roomId, theme }) {
const onConnected = useEffectEvent(() => {
showNotification('Connected!', theme);
});

useEffect(() => {
const connection = createConnection(serverUrl, roomId);
connection.on('connected', () => {
onConnected();
});
connection.connect();
return () => connection.disconnect();
}, [roomId]); // ✅ All dependencies declared (Effect Events aren't dependencies)
// ...

类似于 DOM 事件,Effect 事件总是“看到”最新的 props 和 state。

🌐 Similar to DOM events, Effect Events always “see” the latest props and state.

Effect Events 不应在依赖数组中声明。你需要升级到 eslint-plugin-react-hooks@latest,这样 linter 就不会尝试将它们作为依赖插入。请注意,Effect Events 只能在与它们的 Effect 相同的组件或 Hook 中声明。linter 会验证这些限制。

注意

何时使用 useEffectEvent

🌐 When to use useEffectEvent

你应该对概念上是“事件”的函数使用 useEffectEvent,这些事件恰好是从一个 Effect 触发的,而不是来自用户事件(这就是它被称为“Effect 事件”的原因)。你不需要把所有东西都封装在 useEffectEvent 中,也不需要仅仅为了消除 lint 错误而使用它,因为这可能导致错误。

🌐 You should use useEffectEvent for functions that are conceptually “events” that happen to be fired from an Effect instead of a user event (that’s what makes it an “Effect Event”). You don’t need to wrap everything in useEffectEvent, or to use it just to silence the lint error, as this can lead to bugs.

要深入了解如何思考事件效应,请参见:将事件与效应分离

🌐 For a deep dive on how to think about Event Effects, see: Separating Events from Effects.


cacheSignal

React Server Components

cacheSignal 仅适用于 React 服务器组件

cacheSignal 允许你知道 cache() 的生命周期何时结束:

import {cache, cacheSignal} from 'react';
const dedupedFetch = cache(fetch);

async function Component() {
await dedupedFetch(url, { signal: cacheSignal() });
}

这允许你在结果不再用于缓存时清理或中止工作,例如:

🌐 This allows you to clean up or abort work when the result will no longer be used in the cache, such as:

  • React 已成功完成渲染
  • 渲染已中止
  • 渲染失败

更多信息,请参见 cacheSignal 文档

🌐 For more info, see the cacheSignal docs.


表演曲目

🌐 Performance Tracks

React 19.2 为 Chrome 开发者工具性能分析添加了一组新的 自定义轨迹,以提供有关你的 React 应用性能的更多信息:

🌐 React 19.2 adds a new set of custom tracks to Chrome DevTools performance profiles to provide more information about the performance of your React app:

React 性能跟踪文档解释了跟踪中包含的所有内容,但这里是一个高级概述。

🌐 The React Performance Tracks docs explain everything included in the tracks, but here is a high-level overview.

调度器 ⚛

🌐 Scheduler ⚛

调度器跟踪显示了 React 正在处理的不同优先级的任务,例如用于用户交互的“阻塞”,或 startTransition 内更新的“过渡”。在每个跟踪中,你将看到正在执行的工作类型,例如触发更新的事件,以及该更新的渲染发生的时间。

🌐 The Scheduler track shows what React is working on for different priorities such as “blocking” for user interactions, or “transition” for updates inside startTransition. Inside each track, you will see the type of work being performed such as the event that scheduled an update, and when the render for that update happened.

我们还显示诸如更新被阻塞以等待不同优先级,或者 React 在继续之前等待绘制的信息。调度器跟踪可以帮助你理解 React 如何将你的代码拆分为不同的优先级,以及它完成工作的顺序。

🌐 We also show information such as when an update is blocked waiting for a different priority, or when React is waiting for paint before continuing. The Scheduler track helps you understand how React splits your code into different priorities, and the order it completed the work.

查看 Scheduler track 文档以了解包含的所有内容。

🌐 See the Scheduler track docs to see everything included.

组件 ⚛

🌐 Components ⚛

组件跟踪显示了 React 正在处理的组件树,无论是用于渲染还是运行副作用。在其中,你会看到诸如“挂载”的标签,表示子组件挂载或副作用被挂载,或者“阻塞”,表示由于将工作让出给 React 外部而导致渲染被阻塞。

🌐 The Components track shows the tree of components that React is working on either to render or run effects. Inside you’ll see labels such as “Mount” for when children mount or effects are mounted, or “Blocked” for when rendering is blocked due to yielding to work outside React.

组件追踪帮助你了解组件何时被渲染或运行副作用,以及完成这些工作的时间,以帮助识别性能问题。

🌐 The Components track helps you understand when components are rendered or run effects, and the time it takes to complete that work to help identify performance problems.

查看 组件跟踪文档 以查看包含的所有内容。

🌐 See the Components track docs for see everything included.


新的 React DOM 功能

🌐 New React DOM Features

部分预渲染

🌐 Partial Pre-rendering

在 19.2 中,我们正在添加一个新功能,可以提前预渲染应用的部分内容,然后稍后继续渲染它。

🌐 In 19.2 we’re adding a new capability to pre-render part of the app ahead of time, and resume rendering it later.

这个功能叫做“部分预渲染”,它允许你预渲染应用的静态部分并通过 CDN 提供,然后再继续渲染外壳以随后填充动态内容。

🌐 This feature is called “Partial Pre-rendering”, and allows you to pre-render the static parts of your app and serve it from a CDN, and then resume rendering the shell to fill it in with dynamic content later.

要预先渲染应用以便稍后恢复,首先使用 AbortController 调用 prerender

🌐 To pre-render an app to resume later, first call prerender with an AbortController:

const {prelude, postponed} = await prerender(<App />, {
signal: controller.signal,
});

// Save the postponed state for later
await savePostponedState(postponed);

// Send prelude to client or CDN.

然后,你可以将 prelude shell 返回给客户端,稍后调用 resume 来“恢复”到 SSR 流:

🌐 Then, you can return the prelude shell to the client, and later call resume to “resume” to a SSR stream:

const postponed = await getPostponedState(request);
const resumeStream = await resume(<App />, postponed);

// Send stream to client.

或者你可以调用 resumeAndPrerender 来恢复以获取 SSG 的静态 HTML:

🌐 Or you can call resumeAndPrerender to resume to get static HTML for SSG:

const postponedState = await getPostponedState(request);
const { prelude } = await resumeAndPrerender(<App />, postponedState);

// Send complete HTML prelude to CDN.

更多信息,请参阅新 API 的文档:

🌐 For more info, see the docs for the new APIs:

此外,预渲染 API 现在返回一个 postpone 状态以传递给 resume API。

🌐 Additionally, the prerender apis now return a postpone state to pass to the resume apis.


显著变化

🌐 Notable Changes

为 SSR 批处理 Suspense 边界

🌐 Batching Suspense Boundaries for SSR

我们修复了一个行为上的错误,该错误会导致 Suspense 边界在客户端渲染或从服务器端渲染流式传输时显示不同。

🌐 We fixed a behavioral bug where Suspense boundaries would reveal differently depending on if they were rendered on the client or when streaming from server-side rendering.

从 19.2 开始,React 将对服务器渲染的 Suspense 边界的显示进行短时间的批处理,以允许更多内容同时显示,并与客户端渲染的行为保持一致。

🌐 Starting in 19.2, React will batch reveals of server-rendered Suspense boundaries for a short time, to allow more content to be revealed together and align with the client-rendered behavior.

Diagram with three sections, with an arrow transitioning each section in between. The first section contains a page rectangle showing a glimmer loading state with faded bars. The second panel shows the top half of the page revealed and highlighted in blue. The third panel shows the entire the page revealed and highlighted in blue.
Diagram with three sections, with an arrow transitioning each section in between. The first section contains a page rectangle showing a glimmer loading state with faded bars. The second panel shows the top half of the page revealed and highlighted in blue. The third panel shows the entire the page revealed and highlighted in blue.

以前,在流式服务器端渲染期间,等待内容会立即替换占位内容。

🌐 Previously, during streaming server-side rendering, suspense content would immediately replace fallbacks.

Diagram with three sections, with an arrow transitioning each section in between. The first section contains a page rectangle showing a glimmer loading state with faded bars. The second panel shows the same page. The third panel shows the entire the page revealed and highlighted in blue.
Diagram with three sections, with an arrow transitioning each section in between. The first section contains a page rectangle showing a glimmer loading state with faded bars. The second panel shows the same page. The third panel shows the entire the page revealed and highlighted in blue.

在 React 19.2 中,suspense 边界会在短时间内批处理,以便一起显示更多内容。

🌐 In React 19.2, suspense boundaries are batched for a small amount of time, to allow revealing more content together.

此修复还为在 SSR 期间支持 <ViewTransition> 的 Suspense 做了准备。通过一起显示更多内容,动画可以在更大批次的内容中运行,并避免对紧密连续流入的内容进行动画链式处理。

🌐 This fix also prepares apps for supporting <ViewTransition> for Suspense during SSR. By revealing more content together, animations can run in larger batches of content, and avoid chaining animations of content that stream in close together.

注意

React 使用启发式方法来确保节流不会影响核心网页指标和搜索排名。

🌐 React uses heuristics to ensure throttling does not impact core web vitals and search ranking.

例如,如果总页面加载时间接近2.5秒(这是被认为对LCP来说“良好”的时间),React 将停止批处理并立即显示内容,以确保节流不是未达标指标的原因。

🌐 For example, if the total page load time is approaching 2.5s (which is the time considered “good” for LCP), React will stop batching and reveal content immediately so that the throttling is not the reason to miss the metric.


SSR:Node 的 Web 流支持

🌐 SSR: Web Streams support for Node

React 19.2 为 Node.js 中的流式 SSR 添加了对 Web 流的支持:

🌐 React 19.2 adds support for Web Streams for streaming SSR in Node.js:

以及新的 resume API:

🌐 As well as the new resume APIs:

易犯错误

在 Node.js 中的服务器端渲染中优先使用 Node 流

🌐 Prefer Node Streams for server-side rendering in Node.js

在 Node.js 环境中,我们仍然强烈推荐使用 Node Streams API:

🌐 In Node.js environments, we still highly recommend using the Node Streams APIs:

这是因为在 Node 中,Node 流比 Web 流快得多,而且 Web 流默认不支持压缩,导致用户可能会无意中遗漏流媒体的优势。

🌐 This is because Node Streams are much faster than Web Streams in Node, and Web Streams do not support compression by default, leading to users accidentally missing the benefits of streaming.


eslint-plugin-react-hooks v6

我们还在 recommended 预设中默认发布了带有扁平配置的 eslint-plugin-react-hooks@latest,并选择使用由新 React 编译器驱动的规则。

🌐 We also published eslint-plugin-react-hooks@latest with flat config by default in the recommended preset, and opt-in for new React Compiler powered rules.

要继续使用旧版配置,你可以切换到 recommended-legacy

🌐 To continue using the legacy config, you can change to recommended-legacy:

- extends: ['plugin:react-hooks/recommended']
+ extends: ['plugin:react-hooks/recommended-legacy']

有关编译器启用规则的完整列表,请查看 linter 文档

🌐 For a full list of compiler enabled rules, check out the linter docs.

查看 eslint-plugin-react-hooks 更新日志以获取完整的更改列表

🌐 Check out the eslint-plugin-react-hooks changelog for a full list of changes.


更新默认的 useId 前缀

🌐 Update the default useId prefix

在 19.2 中,我们将默认 useId 前缀从 :r:(19.0.0)或 «r»(19.1.0)更新为 _r_

🌐 In 19.2, we’re updating the default useId prefix from :r: (19.0.0) or «r» (19.1.0) to _r_.

使用一个对 CSS 选择器无效的特殊字符的最初目的是它不太可能与用户编写的 ID 冲突。然而,为了支持视图转换,我们需要确保由 useId 生成的 ID 对 view-transition-name 和 XML 1.0 名称是有效的。

🌐 The original intent of using a special character that was not valid for CSS selectors was that it would be unlikely to collide with IDs written by users. However, to support View Transitions, we need to ensure that IDs generated by useId are valid for view-transition-name and XML 1.0 names.


更新日志

🌐 Changelog

其他显著变化

🌐 Other notable changes

  • react-dom:允许在可提升的样式上使用 nonce #32461
  • react-dom:如果使用 React 拥有的节点作为容器,同时它还有文本内容,则会发出警告 #32774

显著的错误修复

🌐 Notable bug fixes

  • react:将上下文字符串化为 “SomeContext” 而不是 “SomeContext.Provider” #33507
  • react:修复 popstate 事件中 useDeferredValue 无限循环 #32821
  • react:修复当初始值传递给 useDeferredValue 时的一个错误 #34376
  • react:修复在使用客户端操作提交表单时的崩溃问题 #33055
  • react:如果脱水的悬浮边界重新悬浮,则隐藏/取消隐藏其内容 #32900
  • react:在热重载期间避免宽树上的堆栈溢出 #34145
  • react:在各个地方改进组件堆栈 #33629, #33724, #32735, #33723
  • react:修复在 React.lazy 组件中使用 React.use 的一个 bug #33941
  • react-dom:在使用 ARIA 1.3 属性时停止警告 #34264
  • react-dom:修复了在 Suspense 回退中深度嵌套 Suspense 的错误 #33467
  • react-dom:在渲染时中止后挂起时避免挂起 #34192

有关完整的更改列表,请参阅 更新日志

🌐 For a full list of changes, please see the Changelog.


感谢 Ricky Hanlon 撰写这篇文章,Dan AbramovMatt CarrollJack PopeJoe Savona 审阅这篇文章。

🌐 Thanks to Ricky Hanlon for writing this post, Dan Abramov, Matt Carroll, Jack Pope, and Joe Savona for reviewing this post.