
React 19.2 发布
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 支持两种模式:visible 和 hidden。
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 的文档:
react-dom/serverresume:用于 Web Streams。resumeToPipeableStream:用于 Node Streams。
react-dom/staticresumeAndPrerender:用于 Web Streams。resumeAndPrerenderToNodeStream:用于 Node Streams。
此外,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 的支持:
renderToReadableStream现在可用于 Node.jsprerender现在可用于 Node.js
以及新的 resume API:
resume现在可用于 Node.js。resumeAndPrerender现在可用于 Node.js。
陷阱
在 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 名称有效。
变更日志
其他值得注意的变化
值得注意的 bug 修复
react:将 context 字符串化为 "SomeContext" 而不是 "SomeContext.Provider" #33507react:修复popstate事件中无限的useDeferredValue循环问题 #32821react:修复向useDeferredValue传递初始值时的 bug #34376react:修复使用客户端 Actions 提交表单时的崩溃问题 #33055react:如果已 dehydrated 的 Suspense 边界重新挂起,则隐藏/取消隐藏其内容 #32900react:避免在热重载期间宽树上的栈溢出问题 #34145react:在各种地方改进组件堆栈信息 #33629、#33724、#32735、#33723react:修复在React.lazy加载的组件内部使用React.use的 bug #33941react-dom:使用 ARIA 1.3 属性时停止警告 #34264react-dom:修复 Suspense fallback 内部深层嵌套 Suspense 的 bug #33467react-dom:修复在渲染期间中止后挂起时的卡死问题 #34192
对于完整的更改列表,请参阅变更日志。
感谢 Ricky Hanlon 撰写本文,感谢 Dan Abramov、Matt Carroll、Jack Pope 和 Joe Savona 审阅本文。
帮助我们改进文档
发现翻译问题或内容错误?请告诉我们。
