React 19 是 React 于 2024 年 12 月发布的大版本。它新增了 Actions(管理等待状态、错误和表单的异步函数)、useActionState、useOptimistic 和 use 这几个 hook、ref 作为普通 prop,以及由 React 移到文档 head 中的 <title> 和 <meta> 标签。React 19.2(2025 年 10 月)又加入了 <Activity> 和 useEffectEvent。
下面的每个示例都运行在 React 19.2 上。先来看一个 Action:一个传给表单的异步函数,等待状态和返回的错误都交给 useActionState 处理。
输入框留空时点击 Save,可以看到错误提示;再输入一个名字并保存,按钮会显示“Saving...”800 毫秒。整个过程没有用 useState 存错误,没有 isLoading 标志,也没有 e.preventDefault()。
Actions 与表单 action
在 React 18 中,每个表单都要写同样的几步:阻止默认提交、设置加载标志、等待请求、保存错误、清除标志。React 19 把在 transition 中执行异步操作的函数称为 Action,并替你完成这些琐事。
startTransition(以及useTransition)现在可以接收异步函数。在整个函数执行完之前,isPending一直是true。<form action={fn}>会在表单提交时用表单的FormData调用fn。它作为 Action 运行,结束后 React 会重置表单中的非受控字段。<button formAction={fn}>可以给某一个提交按钮单独指定 action。- useActionState 会保存 action 上一次返回的值,所以 action 可以返回错误信息或已保存的记录。
react-dom中的useFormStatus让表单内部的组件无需层层传递 props,就能知道这个表单是否正在提交。它需要外层有一个<form>,所以这里放在静态代码块中展示:
import { useFormStatus } from 'react-dom';
function SubmitButton() {
const { pending } = useFormStatus();
return <button disabled={pending}>{pending ? 'Sending...' : 'Send'}</button>;
}
useOptimistic
useOptimistic 在 Action 还在运行时先显示你预期的结果,Action 结束后再回到真实状态。比如点赞计数可以在点击的瞬间加一,如果请求失败再退回去。
const [optimisticLikes, addOptimisticLike] = useOptimistic(likes, (current, delta) => current + delta);
useOptimistic 页面有一个可运行示例,用的是故意失败的假 API,你可以亲眼看到回滚的过程。
use API
use 可以从 promise 或 context 中读取值。传入 promise 时,它会挂起组件直到 promise resolve,期间由最近的 <Suspense> 显示 fallback。与其他所有 hook 不同,use 可以在 if 或循环中调用,也可以在提前 return 之后调用。
React 19 还允许直接渲染 <ThemeContext value="dark">,不再需要 <ThemeContext.Provider>。下面的示例同时用到了这两点:
ref 作为 prop,以及 ref 清理函数
函数组件现在像接收其他 prop 一样接收 ref。要把 ref 传给内部的 DOM 元素,不再需要用 forwardRef 包裹组件。
ref 回调现在也可以返回一个清理函数。元素从页面上移除时 React 会调用它,和 effect 的清理函数一样。没有返回值的回调仍会像 React 18 那样,在移除时以 null 为参数被调用。
多点几次按钮,再看控制台:每次移除都会打印清理那一行,每次重新出现都会打印 attached to DIV。
文档元数据
你可以在组件的任意位置渲染 <title>、<meta> 和 <link>,React 会把它们移到 <head> 中。页面组件不借助 react-helmet 之类的库,也能设置自己的标题。
预览区没有浏览器标签页可以显示标题,所以 effect 会把 document.title 打印出来。切换文章,打印出的标题也会跟着变。
样式表也有同样的待遇:组件中的 <link rel="stylesheet" href="..." precedence="default" /> 会按 precedence 顺序插入 head,且只插入一次,React 会等它加载完再显示依赖它的内容。被多个组件渲染的 <script async src="..."> 也只会加载一次。react-dom 还导出了 preload、preinit、preconnect 和 prefetchDNS,用于资源提示。
Activity 与 useEffectEvent(React 19.2)
<Activity mode="hidden"> 会隐藏子组件,但不丢弃它们的状态。React 隐藏 DOM,执行 effect 的清理函数,等 mode 重新变成 "visible" 时再全部恢复。它适合用户还会切回来的标签页和面板。
输入一段笔记,切换到 About 再切回来,文字还在,控制台显示 effect 在隐藏期间被清理了。把 <Activity> 换成 {tab === 'notes' && <Notes />},每次切换笔记都会丢失。
useEffectEvent 把一个函数从 effect 的依赖中拿出来。这个函数总能看到最新的 props 和 state,但它们变化时不会让 effect 重新执行:
function ChatRoom({ roomId, theme }) {
const onConnected = useEffectEvent(() => {
showNotification('Connected!', theme);
});
useEffect(() => {
const connection = createConnection(roomId);
connection.on('connected', () => onConnected());
connection.connect();
return () => connection.disconnect();
}, [roomId]); // theme is not a dependency
}
19.2 还为 Server Components 加入了 cacheSignal(告诉你某个 cache() 结果何时不再需要),并在 Chrome DevTools 的性能面板中加入了 Performance Tracks。React 19.1 加入了 captureOwnerStack,这是一个仅在开发环境可用的函数,返回是哪些组件渲染了当前组件。
Server Components 与 Server Functions
Server Components 是提前在服务器上(或在构建时)运行、再把输出发送到浏览器的组件。它们可以是 async 的,可以直接读取数据库,代码也永远不会发送到客户端。Server Functions(最初叫 Server Actions)是用 'use server' 标记的异步函数,客户端组件可以像调用普通函数一样调用它们。两者都需要一个打包工具支持它们的框架,例如 Next.js App Router,所以无法在这个编辑器中运行。
// A Server Component: async, runs on the server only
export default async function Notes() {
const notes = await db.notes.findMany();
return notes.map((note) => <Note key={note.id} note={note} />);
}
// A Server Function, usable as a form action
async function createNote(formData) {
'use server';
await db.notes.create({ text: formData.get('text') });
}
移除和变更的 API
React 19 移除了那些已弃用多年的 API:
| 已移除 | 替代方案 |
|---|---|
ReactDOM.render、ReactDOM.hydrate | react-dom/client 中的 createRoot、hydrateRoot |
unmountComponentAtNode | root.unmount() |
findDOMNode | 在 DOM 元素上使用 ref |
字符串 ref(ref="input") | useRef 或 ref 回调 |
旧版 context(contextTypes、getChildContext) | createContext 配合 static contextType 或 use |
propTypes 检查(现在会被静默忽略) | TypeScript,或你自己的检查 |
函数组件上的 defaultProps | 参数默认值 |
react-dom/test-utils | react 中的 act;其余用 React Testing Library |
react-test-renderer/shallow | react-shallow-renderer 包 |
React.createFactory | JSX |
类组件仍然保留 defaultProps。错误的上报方式也变了:被错误边界捕获的错误只用 console.error 记录一次,未捕获的错误交给 window.reportError,createRoot 还接受 onCaughtError 和 onUncaughtError 选项,让你自己上报。
升级到 React 19
先升级到 React 18.3。它和 18.2 完全相同,只是会对 React 19 需要的弃用 API 和其他变更发出警告,这样你可以在应用仍能正常运行时逐一修复。React 19 还要求使用新版 JSX 转换,Vite 和所有当前的框架都已经在用。然后安装 React 19:
npm install react@^19 react-dom@^19
npm install --save-dev @types/react@^19 @types/react-dom@^19
官方升级指南推荐用 codemod 处理机械性的改动。第一个处理字符串 ref、act 的导入和 ReactDOM.render;第二个把 TypeScript 代码更新为新的类型:
npx codemod@latest react/19/migration-recipe
npx types-react-codemod@latest preset-19 ./src
升级之后,没有什么强迫你立即采用 Actions 或新的 hook。现有的组件、hook 和类组件都能继续工作;你可以把表单一个一个地改成 <form action>。
常见问题
React 19 是什么时候发布的?
React 19 于 2024 年 12 月发布稳定版。之后 React 19.1 于 2025 年 3 月发布,React 19.2 于 2025 年 10 月发布。
React 19 最大的变化有哪些?
Actions(替你处理等待状态、错误和表单的异步函数)、useActionState、useOptimistic 和 use 这几个 hook、ref 作为普通 prop、<Context> 直接作为 provider,以及会被移到文档 head 中的 <title> 和 <meta> 标签。Server Components 也在框架中进入了稳定阶段。
React 19 中 forwardRef 被弃用了吗?
函数组件现在可以像普通 prop 一样接收 ref,所以新代码不再需要 forwardRef。它目前仍然可用,React 团队表示会在未来的版本中将其弃用。
React 19 会让我的 React 18 应用出问题吗?
主要问题来自被移除的 API:ReactDOM.render、findDOMNode、字符串 ref、旧版 context、propTypes,以及函数组件上的 defaultProps。React 19 还要求使用新版 JSX 转换。先升级到 React 18.3,修复它给出的弃用警告,再运行 React 19 的 codemod。
使用 React 19 必须用 Next.js 吗?
不需要。Actions、新的 hook、ref 作为 prop 和元数据都能在用 Vite 搭建的普通客户端应用中使用。只有 Server Components 和 Server Functions 需要支持它们的框架,例如 Next.js。