React 19.2 用起来要注意什么:Activity、useEffectEvent、部分预渲染与 SSR 变更
React 19.2 已发布到 npm,是过去一年里的第三个版本(前两个是 2025 年 12 月的 React 19 和 6 月的 React 19.1)。官方发布说明:react.dev/blog/2025/10/01/react-19-2。
这一版的新增内容按官方分类:
- React 新特性:``、
useEffectEvent、cacheSignal、Performance Tracks - React DOM 新特性:Partial Pre-rendering
- 值得注意的变更:SSR 的 Suspense 边界批处理;SSR 在 Node 上的 Web Streams 支持;eslint-plugin-react-hooks v6;
useId默认前缀变更 - Changelog:react-dom hoistable styles 上的 nonce(#32461);react-dom 警告将有文本内容的 React owned node 作为 Container(#32774)
Activity
Activity 用来替代条件渲染组件分支:
// Before
{isVisible &&
}
// After
19.2 中 Activity 支持两种 mode:
hidden:隐藏 children,卸载 effect,并把所有更新延后到 React 没有其他工作可做时再处理。visible:显示 children,挂载 effect,更新正常处理。
也就是说,隐藏部分可以预渲染并持续渲染,而不影响屏幕上可见内容的性能。
典型用法:渲染用户接下来大概率会导航到的隐藏部分,或者保存用户已经离开的部分的状态。数据、CSS、图片在后台加载,导航会变快;返回导航时输入框之类的状态得以保留。官方后续计划给 Activity 增加更多 mode。
useEffectEvent
useEffect 有个常见模式:把外部系统的事件通知给应用代码。例如 ChatRoom({roomId, theme}) 建立连接,在 'connected' 时调用 showNotification('Connected!', theme)。问题在于,这个"事件"里用到的任何值发生变化,都会让包裹它的 Effect 重新执行——theme 一变,聊天室就重连。roomId 变化重连说得通,theme 变化不该重连。多数人的做法是关掉 lint 规则、把依赖排除掉,但这样 linter 就再也帮不上忙,容易出现依赖过期的 bug。
useEffectEvent 把这段逻辑里"事件"的部分从触发它的 Effect 中拆出来:
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 Event 总是能"看到"最新的 props 和 state。
坑与限制:
- Effect Event 不要写进依赖数组。需要升级到
eslint-plugin-react-hooks@latest,否则 linter 会试图把它插入为依赖。 - Effect Event 只能和"它的" Effect 声明在同一个组件或 Hook 里。这些限制由 linter 校验。
使用时机:只用于概念上属于 Effect 触发的"事件"的函数,而不是用户事件。不要什么都往 useEffectEvent 里包,也不要只是为了消掉 lint 报错而用它,那会引入 bug。详见文档 "Separating Events from Effects"。
cacheSignal
仅适用于 React Server Components。cacheSignal 让你知道 cache() 的生命周期何时结束:
import { cache, cacheSignal } from 'react';
const dedupedFetch = cache(fetch);
async function Component() {
await dedupedFetch(url, { signal: cacheSignal() });
}
当结果不再会被缓存使用时,可以据此清理或中止工作,具体包括三种情况:React 成功完成渲染、渲染被中止、渲染失败。
Performance Tracks
19.2 在 Chrome DevTools 性能面板里新增了一组自定义 track。
- Scheduler ⚛ track:显示 React 在不同优先级下正在做什么,比如用户交互的
"blocking"、startTransition内更新的"transition"。每个 track 内可以看到工作类型、调度这次更新的 event、render 发生的时间;也能看到某次更新是否因为等待其他优先级而阻塞,或者 React 是否在等 paint 再继续。 - Components ⚛ track:显示 React 正在 render 或执行 effect 的组件树。
"Mount"表示 children 挂载或 effect 挂载,"Blocked"表示渲染因为让出给 React 之外的工作而阻塞。
Partial Pre-rendering
React DOM 的新能力:提前预渲染应用的一部分,之后再恢复渲染。把静态部分预渲染出来放到 CDN 上,然后 resume 渲染 shell,补上动态内容。
先带 AbortController 调用 prerender:
const { prelude, postponed } = await prerender(, { signal: controller.signal });
await savePostponedState(postponed);
把 prelude shell 返回给客户端,之后再调用 resume "恢复"成 SSR 流:
const postponed = await getPostponedState(request);
const resumeStream = await resume(, postponed);
或者调用 resumeAndPrerender 恢复成 SSG 用的静态 HTML:
const postponedState = await getPostponedState(request);
const { prelude } = await resumeAndPrerender(, postponedState);
新增 API:
react-dom/server:resume(Web Streams)、resumeToPipeableStream(Node Streams)react-dom/static:resumeAndPrerender(Web Streams)、resumeAndPrerenderToNodeStream(Node Streams)
另外,prerender 系列 API 现在会返回 postpone state,供 resume 系列 API 使用。
SSR 的 Suspense 边界批处理
这一版修掉了一个行为 bug:Suspense 边界在客户端渲染和 SSR 流式渲染下的揭示方式不一致。从 19.2 起,React 会把服务端渲染的 Suspense 边界的揭示批量延迟一小段时间,让更多内容一起出现,行为与客户端渲染对齐。此前在流式 SSR 中,suspense 内容会立刻替换 fallback。这个修复也为 SSR 期间给 Suspense 支持 `` 做了准备。
React 用启发式判断来保证这种节流不影响核心 web vitals 和搜索排名。例如整页加载时间接近 2.5s(LCP 被视为 "good" 的时间)时,React 会停止批处理、立即揭示内容,避免因为节流而丢掉这个指标。
SSR:Node 上的 Web Streams 支持
以下 API 现在可以在 Node.js 中使用:renderToReadableStream、prerender、resume、resumeAndPrerender。
坑:在 Node.js 里做服务端渲染仍然优先用 Node Streams。Node 环境下依然强烈推荐 renderToPipeableStream、resumeToPipeableStream、prerenderToNodeStream、resumeAndPrerenderToNodeStream。原因是 Node Streams 在 Node 里比 Web Streams 快得多,而且 Web Streams 默认不支持压缩,很容易让用户莫名其妙享受不到流式渲染的好处。
eslint-plugin-react-hooks v6
eslint-plugin-react-hooks@latest 已发布,recommended preset 默认使用 flat config,由 React Compiler 驱动的新规则需要手动开启。要继续用旧配置,改成 recommended-legacy:
extends: ['plugin:react-hooks/recommended-legacy']
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 名称合法。
Changelog
- react-dom:hoistable styles 上的 nonce(#32461)
- react-dom:警告将有文本内容的 React owned node 作为 Container(#32774)
完整发布说明见 react.dev/blog/2025/10/01/react-19-2。