知海

React 19.2 发布

React博客-新闻

React 19.2 发布

2025 年 10 月 1 日,作者:React 团队


导语

React 19.2 现已发布到 npm!


这是我们在过去一年中的第三次发布,此前分别是 12 月的 React 19 和 6 月的 React 19.1。在本文中,我们将概述 React 19.2 中的新特性,并重点介绍一些值得注意的变化。

新的 React 特性

<Activity />

<Activity> 允许你将应用拆分为可控制和可优先级的“活动”(activities)。

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

js 复制代码
// 之前
{isVisible && <Page />}

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

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

  • hidden:隐藏子元素,卸载 effects,并延迟所有更新,直到 React 没有其他工作可做。
  • visible:显示子元素,挂载 effects,并允许正常处理更新。

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

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

未来,我们计划为 Activity 添加更多模式以适用于不同的用例。

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


useEffectEvent

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

js {5,11} 复制代码
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 来说则不合理。

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

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

js {2,3,4,9} 复制代码
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]); // ✅ 所有依赖项都已声明(Effect Events 不是依赖项)
  // ...

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

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

注意

何时使用 useEffectEvent

你应该对概念上属于“事件”的函数使用 useEffectEvent,这些函数恰好是从 Effect 中触发的,而不是由用户事件触发的(这就是它被称为“Effect Event”的原因)。你不需要将一切都包装在 useEffectEvent 中,也不应该仅仅为了消除 lint 错误而使用它,因为这可能导致 bug。

关于如何思考 Effect Events 的深入探讨,请参阅:将事件与 Effects 分离


cacheSignal

RSC

cacheSignal 仅适用于 React 服务器组件

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

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

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

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

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

更多信息,请参阅 cacheSignal 文档


性能轨道

React 19.2 向 Chrome DevTools 性能分析文件中添加了一组新的自定义轨道,以提供有关 React 应用性能的更多信息:

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

调度器 ⚛

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

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

查看调度器轨道文档以了解包含的所有内容。

组件 ⚛

组件轨道显示了 React 正在渲染或运行 effects 的组件树。在其中你会看到诸如“Mount”(挂载)用于子组件挂载或 effects 挂载的标签,或“Blocked”(阻塞)用于因让出给 React 外部工作而渲染被阻止的标签。

组件轨道帮助你了解组件何时渲染或运行 effects,以及完成该工作所需的时间,从而帮助识别性能问题。

查看组件轨道文档以了解包含的所有内容。


新的 React DOM 特性

部分预渲染

在 19.2 中,我们添加了一项新功能,可以提前预渲染应用的一部分,并在稍后恢复渲染。

此功能称为“部分预渲染”(Partial Pre-rendering),允许你预渲染应用的静态部分并从 CDN 提供,然后恢复渲染外壳,以便稍后用动态内容填充它。

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

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

// 保存 postponed 状态以供稍后使用
await savePostponedState(postponed);

// 将 prelude 发送到客户端或 CDN。

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

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

// 将流发送到客户端。

或者你可以调用 resumeAndPrerender 来恢复以生成用于 SSG 的静态 HTML:

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

// 将完整的 HTML prelude 发送到 CDN。

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

此外,prerender API 现在会返回一个 postpone 状态,以传递给 resume API。


值得注意的变化

SSR 的 Suspense 边界批处理

我们修复了一个行为 bug,即 Suspense 边界在客户端渲染与服务器端渲染流式传输时的显示方式不同。

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

以前,在流式服务器端渲染期间,Suspense 内容会立即替换 fallback。

在 React 19.2 中,Suspense 边界会在一小段时间内被批处理,以允许更多内容一起显示。

此修复还为应用将来在 SSR 期间支持 <ViewTransition> 与 Suspense 做好准备。通过一起显示更多内容,动画可以以更大的内容批次运行,并避免将流式传输内容接近的动画串联起来。

注意

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

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


SSR:Node.js 的 Web Streams 支持

React 19.2 增加了对在 Node.js 中使用 Web Streams 进行流式 SSR 的支持:

以及新的 resume API:

陷阱

在 Node.js 中进行服务端渲染时优先使用 Node Streams

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

这是因为 Node Streams 在 Node 中比 Web Streams 快得多,而且 Web Streams 默认不支持压缩,导致用户可能意外地无法获得流式传输的好处。


eslint-plugin-react-hooks v6

我们还发布了 eslint-plugin-react-hooks@latest,在 recommended 预设中默认启用 flat config,并为新的 React 编译器支持的规则提供选择加入功能。

要继续使用旧的配置,你可以更改为 recommended-legacy

diff 复制代码
- extends: ['plugin:react-hooks/recommended']
+ extends: ['plugin:react-hooks/recommended-legacy']

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

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


更新默认的 useId 前缀

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

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


变更日志

其他值得注意的变化

  • react-dom:允许在可提升样式上使用 nonce #32461
  • react-dom:当使用的容器是 React 拥有的节点且同时包含文本内容时发出警告 #32774

值得注意的 bug 修复

  • react:将 context 字符串化为 "SomeContext" 而不是 "SomeContext.Provider" #33507
  • react:修复 popstate 事件中无限的 useDeferredValue 循环问题 #32821
  • react:修复向 useDeferredValue 传递初始值时的 bug #34376
  • react:修复使用客户端 Actions 提交表单时的崩溃问题 #33055
  • react:如果已 dehydrated 的 Suspense 边界重新挂起,则隐藏/取消隐藏其内容 #32900
  • react:避免在热重载期间宽树上的栈溢出问题 #34145
  • react:在各种地方改进组件堆栈信息 #33629#33724#32735#33723
  • react:修复在 React.lazy 加载的组件内部使用 React.use 的 bug #33941
  • react-dom:使用 ARIA 1.3 属性时停止警告 #34264
  • react-dom:修复 Suspense fallback 内部深层嵌套 Suspense 的 bug #33467
  • react-dom:修复在渲染期间中止后挂起时的卡死问题 #34192

对于完整的更改列表,请参阅变更日志


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

帮助我们改进文档

发现翻译问题或内容错误?请告诉我们。