React 19 于 2024 年底正式发布,标志着 React 从“客户端 UI 库”向“全栈应用框架”迈出关键一步。Actions 让表单提交与异步状态不再需要手写 loading 与 error 样板,useOptimistic 则把“乐观更新”从手写技巧变成一等公民。本文用可运行示例讲透升级要点、常见坑与生产落地清单,帮助你把现有项目平滑迁移到 React 19。
一、为什么值得升级到 React 19
过去在 React 里处理一次表单提交,至少要手写 useState 管理输入值、loading、error 三套状态,再在 onSubmit 里 preventDefault 并 try/catch。React 19 把这套高频样板收敛成声明式 API:用 <form action={serverAction}> 直接把函数挂到表单上,框架自动处理待定状态与错误边界。与此同时,ref 终于能像普通 prop 一样传递,forwardRef 正式弃用;组件内还能直接写 <title> 与 <meta>,不再依赖 react-helmet 之类的库。对追求工程化效率的团队,这些改动意味着更少的样板与更低的认知负担,也是本文要重点拆解的部分。
如果你正在做前端工程化或 Monorepo 改造,升级 React 19 可以和构建工具、包管理器一起纳入同一轮技术债清理,降低多次升级的回归成本。
二、Actions:把表单提交变成声明式
React 19 引入的 Actions 本质是一类等异步函数,可以是一个在服务端执行的 Server Action,也可以是一个客户端异步函数。useActionState 专门用来管理“提交中 / 提交后状态”,它返回当前状态、绑定到 form 的 action、以及是否 pending 三个值。
2.1 用 useActionState 管理提交状态
// app/actions.ts —— 服务端 Action(Next.js App Router 示例)
"use server";
import { revalidatePath } from "next/cache";
export async function addTodo(
prevState: { error?: string },
formData: FormData
) {
const title = formData.get("title");
if (!title || String(title).trim() === "") {
return { error: "标题不能为空" };
}
// 模拟写入数据库
await db.todos.create({ title: String(title) });
revalidatePath("/todos");
return { error: undefined };
}
上面的 Action 接收上一次的状态和表单数据,返回新的状态。注意它返回的是“状态对象”而不是副作用,这让 React 可以自动把结果喂回 useActionState。
"use client";
import { useActionState } from "react";
import { addTodo } from "./actions";
export function TodoForm() {
const [state, formAction, isPending] = useActionState(
addTodo,
{ error: undefined }
);
return (
<form action={formAction}>
<input name="title" placeholder="请输入待办" />
<button type="submit" disabled={isPending}>
{isPending ? "提交中…" : "添加"}
</button>
{state.error && <p style={{ color: "red" }}>{state.error}</p>}
</form>
);
}
把 formAction 直接赋给 <form action>,提交时 React 会自动把表单数据序列化成 FormData 传给函数,并替你维护 isPending。不再需要 onSubmit + preventDefault 的模板代码。
2.2 useFormStatus 在提交按钮上显示 loading
当提交按钮和表单不在同一组件时,可以用 useFormStatus 读取父级表单的 pending 状态,无需把状态层层透传。
"use client";
import { useFormStatus } from "react";
export function SubmitButton() {
const { pending } = useFormStatus();
return (
<button type="submit" disabled={pending}>
{pending ? "保存中…" : "保存"}
</button>
);
}
三、useOptimistic:乐观更新的官方解法
点赞、收藏这类操作,用户期待“点了立刻变”,等到服务端确认再固化。过去要手写临时状态并在失败时回滚,React 19 的 useOptimistic 把这套模式标准化:它在数据确认前先展示乐观值,一旦 use 的源数据更新或组件重渲染,就自动回到真实值,失败时也能自然回滚。
"use client";
import { useOptimistic, useState } from "react";
export function LikeButton({ initialLikes }: { initialLikes: number }) {
const [likes, setLikes] = useState(initialLikes);
const [optimisticLikes, addOptimisticLike] = useOptimistic(
likes,
(current, delta: number) => current + delta
);
async function like() {
addOptimisticLike(1); // 立即 +1,UI 先行
const res = await fetch("/api/like", { method: "POST" });
if (res.ok) setLikes((n) => n + 1); // 服务端确认后固化
}
return <button onClick={like}>❤️ {optimisticLikes}</button>;
}
乐观值只在下次渲染前生效,因此务必在请求成功后用真实状态覆盖,否则会出现“数字跳回再跳上去”的闪烁。
四、use():在组件里直接读 Promise 与 Context
React 19 新增的 use API 允许在渲染期间读取 Promise 或 Context,配合 Suspense 实现“边渲染边等待”。它和 Hook 的最大区别是可以在条件语句、循环里调用,更灵活。
import { use } from "react";
export function Comments({ commentsPromise }: { commentsPromise: Promise<Comment[]> }) {
// use() 会“暂停”组件等待 Promise 完成,由最近的 Suspense 兜底
const comments = use(commentsPromise);
return (
<ul>
{comments.map((c) => (
<li key={c.id}>{c.body}</li>
))}
</ul>
);
}
把数据请求和组件放在一起,配合路由层的流式渲染(Streaming SSR),首屏不必等齐所有数据,体验显著改善。
五、ref 终于能直接当 props 传了
过去要把 ref 透传给子组件必须用 forwardRef 包裹,写起来啰嗦且类型推导别扭。React 19 支持把 ref 当作普通 prop 传递,forwardRef 已弃用。
// React 18:必须 forwardRef
const Input = forwardRef<HTMLInputElement>((props, ref) => (
<input ref={ref} {...props} />
));
// React 19:ref 就是普通 prop,无需 forwardRef
function Input({ ref, ...props }: { ref?: React.Ref<HTMLInputElement> }) {
return <input ref={ref} {...props} />;
}
升级时建议逐步替换 forwardRef,但主流组件库(如 MUI、Ant Design)需要等其发布兼容 React 19 的版本,过渡期可暂时共存。
六、组件内直接写 title 与 meta
React 19 内置了对文档元信息的支持,在组件里写 <title>、<meta>、<link> 会被自动提升到 <head>,无需再引入专门的 SEO 库。
export function ProductPage({ name }: { name: string }) {
return (
<>
<title>{name} · 限时折扣</title>
<meta name="description" content={`购买 ${name},今日五折`} />
<h1>{name}</h1>
</>
);
}
这对做 SEO 的技术博客或营销页尤其友好,页面级元信息跟着组件走,结构更清晰。
七、生产升级检查清单
下面这张表把升级步骤、说明与潜在风险汇总,建议作为发布前核对项逐项打勾。
| 步骤 | 说明 | 风险与对策 |
|---|---|---|
| 升级依赖 | react / react-dom 升到 19,并校验第三方库兼容性 | 旧库未适配会报 exports 错误,先升主依赖再处理 |
| 替换 forwardRef | 逐步去掉 forwardRef,改用 ref 直接传 | 大型组件库需等待官方更新,过渡期共存 |
| 类型检查 | @types/react 升 19,修复 ref / children 类型 | TS 报错集中在 ref 与 children 推断 |
| 测试回归 | 用 Vitest + Testing Library 跑组件测试 | 关注事件与 Suspense 行为变化 |
| 监控报错 | 关注 hydration 警告与控制台错误 | 服务端/客户端不一致会被框架标记 |
| 渐进发布 | 先小流量灰度再全量 | 保留快速回滚能力 |
八、常见误区与排障
| 现象 | 根因 | 解决 |
|---|---|---|
| useOptimistic 数字闪烁 | 请求成功后未用真实状态覆盖 | 在 fetch 成功后 setLikes 固化 |
| hydrate 不匹配 | 服务端注入了不同元信息/随机数 | 元信息移到组件内统一管理,禁用随机渲染 |
| forwardRef 组件报错 | 库版本未适配 React 19 | 升级该库或暂时锁 React 18 子依赖 |
| use() 在事件里调用报错 | use 只能在渲染期调用 | 改为在组件顶层或 Suspense 子组件使用 |
性能方面,React 19 还对列表渲染、ref 清理函数做了优化。若你的项目存在重渲染瓶颈,可结合 React 性能优化的既有手段(如 memo、useMemo)与本文新特性一起治理。
开发阶段用 VS Code 配合 TypeScript 能最快发现类型迁移问题;用 Bun 这类极速运行时跑 dev server 与测试,能明显缩短反馈循环。测试回归建议在 GitHub Actions 里做成流水线,每次 PR 自动跑 Vitest 套件,把升级风险挡在合并前。
相关阅读:React 性能优化实战:Hooks 陷阱与重渲染治理,前端工程化进阶:pnpm Monorepo 与微前端实战,前端自动化测试:Vitest 与 Playwright 实战,VS Code 高效开发环境配置,GitHub Actions 持续集成实战,Bun 实战:全功能 JavaScript 运行时与极速工具链。




