介绍
React 是一个用于创建用户界面的 JavaScript 库。
继 12 月的 React 19 和 6 月的 React 19.1 之后,10月1号React发布新版本v19.2。
变化
新功能点:
- Activity
- useEffectEvent
- cacheSignal
- Performance Tracks
新的ReactDom功能
- 部分预渲染
其他变化
- 对SSR暂停边界进行批处理
- SSR:对Node的Web Stream支持
- eslint-plugin-react-hooks V6
- 更新默认userId前缀
React新功能
Activity
在React19.2中,Activity支持两种模式,可见和隐藏
- hidden:隐藏子项,卸载效果,并推迟更新,直到React没有任何东西处理
- Visible :显示子项,挂载效果,并允许正常处理更新。
这意味着您可以预渲染并继续渲染应用程序的隐藏部分,而不会影响屏幕上可见的任何内容的性能。
您可以使用 Activity 来呈现用户可能导航到的应用程序的隐藏部分,或者保存用户导航离开的部分的状态。这有助于通过在后台加载数据、CSS 和图像来加快导航速度,并允许返回导航来维护输入字段等状态。
未来,我们计划为不同的用例向 Activity 添加更多模式。
useEffectEvent
当前仅在 React Canary 和 实验发行版中可用
是一个 React Hook,它允许你从 Effects 中提取非反应式逻辑到一个称为 Effect Event 的可重用函数中。
参数
callback
:包含效果事件逻辑的函数。当你使用useEffectEvent
定义一个 Effect Event 时,回调
在调用时总是从 props 和 state 访问最新的值。这有助于避免关闭过时的问题。
返回
返回一个 Effect Event 函数。可以在 useEffect
、useLayoutEffect
或 useInsertionEffect
中调用此函数。
注意
- 仅在 Effects 内部调用: 效果事件只能在效果中调用。在使用它们的效果之前定义它们。不要将它们传递到其他组件或挂钩。
- 不是依赖项快捷方式: 不要使用
useEffectEvent
来避免在 Effect 的依赖项数组中指定依赖项。这可以隐藏错误并使您的代码更难理解。如果需要,首选显式依赖项或使用 refs 来比较以前的值。 - 用于非反应式逻辑: 仅使用
useEffectEvent
提取不依赖于更改值的逻辑。
用法
读取最新的pros和state
通常,当您访问 Effect 中的响应式值时,必须将其包含在依赖项数组中。这可以确保你的效果在该值发生变化时再次运行,这通常是所需的行为。
但在某些情况下,你可能希望读取 Effect 中的最新 props 或 state,而不会导致 Effect 在这些值发生变化时重新运行。
javascript
import { useEffect, useContext, useEffectEvent } from 'react';
function Page({ url }) {
const { items } = useContext(ShoppingCartContext);
const numberOfItems = items.length;
const onNavigate = useEffectEvent((visitedUrl) => {
logVisit(visitedUrl, numberOfItems);
});
useEffect(() => {
onNavigate(url);
}, [url]);
return(
<div>3333</div>
)
}
cacheSignal
React 服务器组件
cacheSignal
目前仅与 React 服务器组件一起使用。
cacheSignal
允许您知道 cache()
生命周期何时结束。
javascript
import {cache, cacheSignal} from 'react';
const dedupedFetch = cache(fetch);
async function Component() {
await dedupedFetch(url, { signal: cacheSignal() });
}
注意:
cacheSignal
目前仅用于 React 服务器组件。在客户端组件中,它将始终返回null
。将来,当客户端缓存刷新或失效时,它也将用于客户端组件。您不应假设它在客户端上始终为 null。- 如果在渲染之外调用,
cacheSignal
将返回null
,以明确当前作用域不会永久缓存。
这允许你在结果不再用于缓存时清理或中止工作,例如:
- React 已成功完成渲染
- 渲染已中止
- 渲染失败
Performance Tracks
React 19.2 为 Chrome DevTools 性能配置文件添加了一组新的自定义轨道,以提供有关 React 应用程序性能的更多信息:

React Performance Tracks 文档解释了轨道中包含的所有内容。
Scheduler(调度器)
Scheduler 轨道显示了 React 针对不同优先级所做的工作,例如用户交互的"阻止",或 startTransition 内部更新的"过渡"。在每个轨道中,你将看到正在执行的工作类型,例如计划更新的事件,以及该更新的渲染发生时间。
我们还显示一些信息,例如何时阻止更新等待不同的优先级,或者 React 何时在继续之前等待绘制。Scheduler 轨道可帮助您了解 React 如何将代码拆分为不同的优先级,以及它完成工作的顺序。
请参阅 Scheduler 轨道文档以查看包含的所有内容。
Components(组件)
组件跟踪显示 React 正在处理的组件树,无论是为了渲染还是运行效果。在里面,你会看到诸如 "Mount" 标签,表示子组件挂载或效果挂载,或者 "Blocked" 标签,表示由于让步给 React 外部的工作而阻止渲染。
组件跟踪帮助你了解组件何时被渲染或运行效果,以及完成该工作的所需时间,以帮助识别性能问题。
参阅组件轨道文档以查看包含的所有内容。
React Dom 的新功能
部分预渲染
在 19.2 中新增了一个能力,可以在提前渲染应用的一部分,然后稍后恢复渲染。这个功能称为 "部分预渲染",允许你预先渲染应用的静态部分并从 CDN 提供,然后稍后恢复渲染外壳以填充动态内容。
要预渲染一个稍后可恢复的应用,首先调用 prerender 并传入一个 AbortController:
javascript
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 流:
javascript
const postponed = await getPostponedState(request);
const resumeStream = await resume(<App />, postponed);
// Send stream to client.
或者您可以调用 resumeAndPrerender
来恢复以获取 SSG 的静态 HTML:
javascript
const postponedState = await getPostponedState(request);
const { prelude } = await resumeAndPrerender(<App />, postponedState);
// Send complete HTML prelude to CDN.
有关详细信息,请参阅新 API 的文档:
-
react-dom/server
resume
:用于 Web Streams。resumeToPipeableStream
的节点流。
-
react-dom/static
resumeAndPrerender
用于 Web 流。resumeAndPrerenderToNodeStream
用于节点流。
此外,预呈现 API 现在返回延迟状态
以传递给恢复
API。
显著变化
SSR 中的 Suspense 边界批处理
修复了一个行为性错误,该错误导致 Suspense 边界会根据是在客户端渲染还是从服务器端渲染流式传输而显示不同的内容。从 19.2 开始,React 将在短时间内批量显示服务器渲染的 Suspense 边界,以允许更多内容一起显示并与客户端渲染的行为保持一致。

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

在 React 19.2 中,Suspense 边界会在短时间内进行批处理,以允许一起揭示更多内容。
此修复还为支持 SSR 中的 <ViewTransition>
准备了应用程序。通过一起揭示更多内容,动画可以在更大的内容批次中运行,并避免链式动画内容流接近在一起。
注意:React 使用启发式方法来确保节流不会影响核心网页指标和搜索排名。例如,如果页面总加载时间接近 2.5 秒(这是被认为是 LCP 的 "良好" 时间),React 将停止批处理并立即揭示内容,以确保节流不会成为错过指标的原因。
SSR:对 Node 的 Web Streams 支持
React 19.2 增加了对 Web Streams 的支持,用于在 Node.js 中流式传输 SSR:
renderToReadableStream
现在可用于 Node.jsprerender
现在可用于 Node.js
以及新的 resume
API:
-
resume
适用于 Node.js。 -
resumeAndPrerender
适用于 Node.js。在 Node.js 中优先使用 Node Streams 进行服务器端渲染
在 Node.js 环境中,官方仍然强烈建议使用 Node Streams API:
renderToPipeableStream
resumeToPipeableStream
prerenderToNodeStream
resumeAndPrerenderToNodeStream
这是因为 Node Streams 比 Node 中的 Web Streams 快得多,并且 Web Streams 默认不支持压缩,导致用户意外错过了流式传输的好处
eslint-plugin-react-hooks v6
还发布了 eslint-plugin-react-hooks@6.1.1
,默认情况下在 recommended
预设中使用扁平配置,并为新的 React 编译器驱动规则启用选择加入。
要继续使用旧配置,可以更改为 recommended-legacy
:
less
- 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
:将上下文字符串化为"SomeContext"而不是"SomeContext.Provider" #33507react
:修复 popstate 事件中的无限 useDeferredValue 循环 #32821react
:修复将初始值传递给 useDeferredValue 时的错误 #34376react
:修复使用客户端作提交表单时崩溃的问题 #33055react
:如果脱水悬念边界重新悬浮,则隐藏/取消隐藏它们的内容 #32900react
: 避免在热重载期间在宽树上出现堆栈溢出 #34145react
:改进各个地方的组件堆栈 #33629, #33724, #32735, #33723react
:修复 React.lazy-ed 组件中 React.use 的错误 #33941react-dom
:使用 ARIA 1.3 属性时停止警告 #34264react-dom
:修复 Suspense 后备中深度嵌套 Suspense 的错误 #33467react-dom
:渲染时中止后避免挂起 #34192
有关更改的完整列表,请参阅更新日志。
demo
js
import { useState,Activity,useEffectEvent,useEffect } from 'react';
export default function App() {
const [isShow, setIsShow] = useState(true);
const [theme, setTheme] = useState('light');
const [count, setCount] = useState(0);
const onLog = useEffectEvent(() => {
console.log(333,theme)
})
useEffect(() => {
onLog()
}, [count])
return (
<>
<h3>Activity</h3>
<Activity mode={isShow ? 'visible' : 'hidden'}>
eeeee
</Activity>
<button onClick={() => setIsShow(!isShow)}>
点我
</button>
<h3>useEffectEvent</h3>
<button onClick={() => setCount(count + 1)}>
点我2
</button>
<button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
点我3
</button>
</>
);
}
```js
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import "./styles.css";
import App from "./App";
const root = createRoot(document.getElementById("root"));
root.render(
<StrictMode>
<App />
</StrictMode>
);
``
```js
{
"name": "react.dev",
"version": "0.0.0",
"main": "/src/index.js",
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test --env=jsdom",
"eject": "react-scripts eject"
},
"dependencies": {
"react": "^19.2.0",
"react-dom": "^19.2.0",
"react-scripts": "^5.0.0"
},
"devDependencies": {},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
}
}