React 19 实战:并发特性、Actions 与新 Hooks
React 19 为什么值得升级
React 19 把过去几年实验性的并发特性正式收敛为稳定 API,并补齐了「表单 / 数据变更」这一长期短板。核心变化可以概括为一句话:用 Action 包裹「写操作」,用并发原语包裹「读操作」,让 UI 在等待中不卡顿。
| 能力 | React 18 | React 19 |
|---|---|---|
| Server Components | 实验(Next.js 等框架) | 成为文档级稳定特性 |
| 表单提交 | 手写 useState + onSubmit |
useActionState / <form action> |
| 读取 Promise / Context | use 不适用 |
use() 可在渲染中读 Promise/Context |
| ref | 只能 useRef + forwardRef |
可直接作为 Props 传递 |
| 乐观更新 | 手写状态切换 | useOptimistic |
| 并发过渡 | useTransition |
useTransition + useDeferredValue 增强 |
Actions:把「写操作」变成一等公民
Action 是一个「可以被 React 等待」的函数。配合 <form action> 或 useActionState,React 会自动管理 pending 状态并禁用提交按钮。
"use client";
import { useActionState } from "react";
async function subscribe(prevState: { ok: boolean; msg: string }, formData: FormData) {
const email = formData.get("email");
// 这里放真正的服务端调用(Server Action 或 fetch)
const res = await fetch("/api/subscribe", {
method: "POST",
body: JSON.stringify({ email }),
});
if (!res.ok) return { ok: false, msg: "订阅失败,请重试" };
return { ok: true, msg: "订阅成功!" };
}
export function SubscribeForm() {
const [state, formAction, pending] = useActionState(subscribe, {
ok: false,
msg: "",
});
return (
<form action={formAction}>
<input name="email" type="email" required />
<button disabled={pending}>{pending ? "提交中…" : "订阅"}</button>
<p>{state.msg}</p>
</form>
);
}
useFormStatus 则让它能在子组件里拿到父表单的状态,无需层层透传:
import { useFormStatus } from "react-dom";
function SubmitButton() {
const { pending } = useFormStatus();
return <button disabled={pending}>{pending ? "保存中…" : "保存"}</button>;
}
配置类数据(如接口返回的表单默认值)可先用 JSON 格式化 工具整理,再回填到
initialState,避免手写大对象出错。
use:在渲染中读 Promise 与 Context
use() 是 React 19 引入的「渲染期读取」原语:它能直接 await 一个 Promise,也能读 Context,并且会在 Suspense 边界处挂起。
import { use } from "react";
function Comments({ commentsPromise }: { commentsPromise: Promise<Comment[]> }) {
const comments = use(commentsPromise); // 挂起直到 resolve
return (
<ul>
{comments.map((c) => (
<li key={c.id}>{c.body}</li>
))}
</ul>
);
}
注意:use() 必须在组件或 Hook 的渲染阶段调用,不能放在 try/catch 之外的条件分支里——它不是一个普通函数,而是告诉 React「这里可能要等」。
并发渲染:让等待不卡 UI
useTransition:把「慢更新」标记为非紧急
import { useTransition, useState } from "react";
function Search() {
const [query, setQuery] = useState("");
const [list, setList] = useState<Item[]>([]);
const [isPending, startTransition] = useTransition();
function onChange(e: React.ChangeEvent<HTMLInputElement>) {
const value = e.target.value;
setQuery(value); // 输入框立刻响应
startTransition(() => {
setList(filterExpensive(value)); // 重计算包在 transition 里
});
}
return (
<div>
<input value={query} onChange={onChange} />
<ul style={{ opacity: isPending ? 0.6 : 1 }}>
{list.map((i) => (
<li key={i.id}>{i.name}</li>
))}
</ul>
</div>
);
}
useDeferredValue:自动「延迟」一个值
当输入与渲染解耦时,useDeferredValue 更省心:
const deferred = useDeferredValue(query);
const results = useMemo(() => filterExpensive(deferred), [deferred]);
两者的区别:useTransition 包裹「一段状态更新」,useDeferredValue 派生「一个被延迟的值」,通常和 useMemo 配合。
useOptimistic:乐观更新开箱即用
import { useOptimistic, useRef } from "react";
type Msg = { id: number; text: string; sending?: boolean };
function Chat() {
const [messages, setMessages] = useOptimistic<Msg[]>([]);
const input = useRef<HTMLInputElement>(null);
async function send(formData: FormData) {
const text = formData.get("text") as string;
// 立即乐观插入
setMessages((list) => [...list, { id: Date.now(), text, sending: true }]);
await fetch("/api/chat", { method: "POST", body: JSON.stringify({ text }) });
}
return (
<form action={send}>
<input ref={input} name="text" />
{messages.map((m) => (
<p key={m.id}>{m.text}{m.sending ? "(发送中)" : ""}</p>
))}
</form>
);
}
ref 作为 Props 与文档元数据
ref 不再是特例
function MyInput({ ref }: { ref: React.Ref<HTMLInputElement> }) {
return <input ref={ref} />;
}
// 使用:<MyInput ref={myRef} /> —— 不再需要 forwardRef
文档元数据原生支持
import { Title, Meta } from "react-dom";
function Page() {
return (
<>
<Title>页面标题</Title>
<Meta name="description" content="页面描述" />
</>
);
}
从 React 18 平滑迁移
- 升级依赖:
npm i react@19 react-dom@19,并升级@types/react。 - 替换
forwardRef:直接把ref写进 Props 即可,旧写法在 19 中已被弃用但暂兼容。 - 落地 Actions:把「提交后 setState + loading」的表单迁移到
useActionState。 - 用
use收口异步:Server Components 下把数据 Promise 传给子组件,用use()读取。 - 接入并发:列表/搜索类交互用
useDeferredValue或useTransition提升响应感。
常见问题 FAQ
Q1:Actions 只能在 Server Component 里用吗?
不是。客户端也能用 useActionState 包裹普通异步函数;Server Action 只是其中一种「函数直接跑在服务端」的形式。
Q2:use() 和 await 有什么区别?
await 是 JS 运行时语义,use() 是 React 的渲染语义:它会在 Suspensive 边界挂起,并能被并发调度打断重渲染。
Q3:useTransition 和 useDeferredValue 选哪个?
要「延迟一整段更新」用 useTransition;要「基于某个快速变化的值派生慢结果」用 useDeferredValue + useMemo。
Q4:React 19 还支持 IE 吗?
不支持,且已彻底移除旧版遗留行为。现代浏览器环境是前提。
Q5:乐观更新失败怎么回滚?
useOptimistic 会在传入的 Action 抛错时自动回滚到真实状态;你只需保证 setMessages 的「真实数据源」在失败时不变即可。
工具推荐
在 React 19 开发中,以下 工具库 能帮上忙:
- JSON 格式化 — 整理接口返回值与表单初始状态
- 颜色转换 — HEX/RGB/HSL 互转,配合主题
- 颜色对比度 — 校验按钮/文本 WCAG 可访问性
- HTTP 状态码 — 排查 Action 接口返回码
React 19 的精髓不是「又多了几个 Hook」,而是把「写操作(Action)」与「读操作(并发/use)」用统一心智模型管起来:UI 在等待数据时不卡顿,在提交变更时状态自管。掌握 Action + 并发原语,你就握住了现代 React 的钥匙。
本站提供浏览器本地工具,免注册即可试用 →