
一句话:React 19 的主线是「把异步与表单做成一等公民」——Actions(
useActionState/useFormStatus/useOptimistic)、useAPI、ref作为普通 prop、文档元数据提升;同时引入 React Compiler(自动记忆化)与 Server Components 的正式落地。
一、总览
| 特性 | 内容 | 价值 |
|---|---|---|
| Actions | <form action={fn}> + useActionState + useFormStatus + useOptimistic |
统一「提交 + loading + 错误 + 乐观更新」 |
use |
在渲染中读取 Promise 与 Context | 配合 Suspense 做数据获取 |
ref 作为 prop |
函数组件可直接接收 ref(不再必须 forwardRef) |
少一层包装 |
ref 清理函数 |
回调 ref 可返回清理函数 | 更自然的资源释放 |
<Context> 作 Provider |
可直接 <ThemeContext value={...}> |
少写 .Provider |
| 文档元数据提升 | <title> / <meta> / <link> 自动提升到 <head> |
不用手写 Head 管理 |
| 样式表与脚本 | <link rel="stylesheet" precedence>、<script async> 的去重与顺序管理 |
更好的加载控制 |
| Server Components | 正式支持(稳定) | 减少客户端包体积、直接访问数据库 |
| Server Actions | "use server" 标记的函数可被表单直接调用 |
少写一层接口 |
| React Compiler | 编译时自动记忆化 | 减少手写 useMemo/useCallback |
| 移除的 API | defaultProps(函数组件)、字符串 ref、propTypes、ReactDOM.render 等 |
清理历史包袱 |
二、Actions:表单的现代写法
// ① 最基础:<form action> 直接接收函数(React 会自动处理 preventDefault 与 reset)
function SimpleForm() {
async function handleSubmit(formData: FormData) {
'use server'; // Server Action(或普通 async 函数)
await db.createTodo({ text: formData.get('text') as string });
}
return (
<form action={handleSubmit}>
<input name="text" required />
<button type="submit">添加</button>
</form>
);
}
// ② useActionState:统一管理「提交结果 + loading」
import { useActionState } from 'react';
function SignupForm() {
const [state, formAction, isPending] = useActionState(
async (prevState: State, formData: FormData) => {
const res = await api.signup(formData);
if (!res.ok) return { error: '注册失败,请重试' };
return { success: true, error: '' };
},
{ error: '', success: false } // 初始 state
);
return (
<form action={formAction}>
<input name="email" type="email" required />
<input name="password" type="password" required />
<button disabled={isPending}>{isPending ? '提交中…' : '注册'}</button>
{state.error && <p role="alert">{state.error}</p>}
</form>
);
}
// ③ useFormStatus:在「子组件」里读取父级 form 的提交状态
import { useFormStatus } from 'react';
function SubmitButton() {
const { pending, data, method, action } = useFormStatus();
return <button disabled={pending}>{pending ? '提交中…' : '提交'}</button>;
}
// ⚠️ 必须在 <form> 的子组件里调用(它读的是「最近的 form」)
function Form() {
return (
<form action={myAction}>
<input name="x" />
<SubmitButton /> {/* ⭐ 这里能读到 pending */}
</form>
);
}
// ④ useOptimistic:乐观更新
import { useOptimistic } from 'react';
function Comments({ comments, addComment }: { comments: Comment[]; addComment: (text: string) => Promise<void> }) {
const [optimisticComments, addOptimisticComment] = useOptimistic(
comments,
(current, newText: string) => [...current, { id: 'temp', text: newText, pending: true }]
);
async function formAction(formData: FormData) {
const text = formData.get('text') as string;
addOptimisticComment(text); // ⭐ 立即显示
await addComment(text); // 后台提交(失败时 React 会自动回滚)
}
return (
<>
<ul>
{optimisticComments.map((c) => (
<li key={c.id} style={{ opacity: 'pending' in c && c.pending ? 0.5 : 1 }}>{c.text}</li>
))}
</ul>
<form action={formAction}>
<input name="text" />
<button>发送</button>
</form>
</>
);
}
| API | 作用 | 注意 |
|---|---|---|
<form action={fn}> |
提交时调用 fn(formData) |
自动处理 preventDefault 与表单重置 |
useActionState |
管理 action 的返回值 + pending |
返回 [state, action, isPending] |
useFormStatus |
读取所属 form 的提交状态 | 必须在 form 的子组件里用 |
useOptimistic |
乐观更新(自动回滚) | 需在 action 内部调用 addOptimistic |
| Server Actions | "use server" 标记的函数 |
可直接在表单里调用服务端逻辑 |
三、use:在渲染中读取 Promise 与 Context
// ① 读 Promise(配合 Suspense)
import { use, Suspense } from 'react';
function Comments({ commentsPromise }: { commentsPromise: Promise<Comment[]> }) {
const comments = use(commentsPromise); // ⭐ 挂起直到 resolve
return <ul>{comments.map((c) => <li key={c.id}>{c.text}</li>)}</ul>;
}
function Page() {
const commentsPromise = fetchComments(); // ⚠️ 不要在组件里创建(会每次新建)
return (
<Suspense fallback={<Skeleton />}>
<Comments commentsPromise={commentsPromise} />
</Suspense>
);
}
// ② 读 Context(可以在条件语句里用!与 useContext 不同)
import { use } from 'react';
function Component({ showTheme }: { showTheme: boolean }) {
if (showTheme) {
const theme = use(ThemeContext); // ✅ 允许(use 不是 Hook 规则限制的对象)
return <div>{theme}</div>;
}
return <div>无主题</div>;
}
// ⚠️ 虽然允许,但仍要谨慎(会让组件对 Context 的依赖变得隐式)
// ③ 也可以在循环里用(这是与 useContext 的重要差异)
{items.map((item) => {
const theme = use(ThemeContext); // ✅ 合法
return <Row key={item.id} theme={theme} />;
})}
`use` 与其它 Hook 的差异:
| 维度 | `useContext` | `use(Context)` |
| 可否在条件/循环里 | ❌ | ✅ |
| 可否在普通函数里 | ❌ | ✅(但仍要在渲染期间调用) |
| 读 Promise | ❌ | ✅ |
⚠️ 但 `use` 仍然不能「在事件处理/Effect 里读 Promise」——
Promise 的读取必须在渲染期间发生(否则会失去 Suspense 的意义)。
四、ref 作为普通 prop(不再必须 forwardRef)
// ✅ React 19:函数组件直接把 ref 当 props 接收
interface Props {
label: string;
ref?: React.Ref<HTMLInputElement>;
}
function MyInput({ label, ref }: Props) {
return (
<label>
{label}
<input ref={ref} />
</label>
);
}
// 使用(与之前一样)
const ref = useRef<HTMLInputElement>(null);
<MyInput label="用户名" ref={ref} />;
// ✅ 回调 ref 现在可以返回「清理函数」
<input
ref={(el) => {
el?.focus();
return () => {
console.log('清理'); // 卸载或 ref 变化时调用
};
}}
/>
// 之前(React 18)需要用「额外的一次调用(传 null)」来模拟清理:
<input
ref={(el) => {
if (el) el.focus();
else console.log('清理'); // ⚠️ 回调会以 null 再次调用
}}
/>
⚠️ 注意事项:
① forwardRef 仍然可用(向后兼容),但不再必需
② 如果用了 TS,@types/react 需要 19 版本才能正确推导「ref 作为 prop」
③ 「ref 是普通 prop」意味着它【可以被展开传递】:
<MyInput {...props} /> 里如果 props 有 ref,会正常传递
五、Context 作为 Provider
// React 18:必须用 .Provider
<ThemeContext.Provider value={theme}>
<App />
</ThemeContext.Provider>
// ✅ React 19:可以直接用 Context 本身
<ThemeContext value={theme}>
<App />
</ThemeContext>
// 两种写法都兼容
六、文档元数据提升
// ✅ React 19:<title> / <meta> / <link> 会自动提升到 <head>(不需要额外库)
function BlogPost({ post }: { post: Post }) {
return (
<article>
<title>{post.title}</title>
<meta name="description" content={post.excerpt} />
<meta property="og:title" content={post.title} />
<link rel="canonical" href={`https://example.com/posts/${post.slug}`} />
<h1>{post.title}</h1>
{post.body}
</article>
);
}
// ✅ 样式表与脚本也有更好的处理
<link rel="stylesheet" href="/style.css" precedence="default" />
// ↑ React 会去重、并按 precedence 排序
<script async src="/analytics.js" />
// ↑ 去重处理(同一个 src 只加载一次)
收益:
① 不再需要 react-helmet / next/head 这类库来做「元数据管理」
② 支持 SSR 流式渲染(元数据可以在流中被提升)
③ 重复的 <link> / <script> 会被自动去重
七、Server Components 与 Server Actions
// ① Server Component(默认):在服务端执行(可以 async)
// app/posts/page.tsx
import { db } from '@/lib/db';
export default async function PostsPage() {
const posts = await db.post.findMany(); // ⭐ 直接访问数据库(代码不进客户端包)
return (
<ul>
{posts.map((p) => <li key={p.id}>{p.title}</li>)}
</ul>
);
}
// ② Client Component:用 "use client" 标记
'use client';
import { useState } from 'react';
export function LikeButton({ postId }: { postId: string }) {
const [liked, setLiked] = useState(false); // ⭐ 有状态/事件,必须在客户端
return <button onClick={() => setLiked((v) => !v)}>{liked ? '已赞' : '点赞'}</button>;
}
// ③ Server Actions:'use server' 标记的函数可以被表单/按钮直接调用
'use server';
export async function createPost(formData: FormData) {
const title = formData.get('title') as string;
await db.post.create({ data: { title } });
revalidatePath('/posts');
}
// 客户端组件里使用
import { createPost } from './actions';
function NewPostForm() {
return (
<form action={createPost}>
<input name="title" />
<button>创建</button>
</form>
);
}
Server Components 的边界规则(重点):
① Server Component 不能有状态、事件处理器、浏览器 API
② Client Component 用 "use client" 标记,并且【它导入的所有模块都会变成客户端代码】
→ 所以要尽量把边界【下沉到叶子组件】
③ Server Component 可以渲染 Client Component(传递「可序列化的 props」)
④ Client Component 不能直接导入 Server Component(但可以通过 children 传入)
八、React Compiler(自动记忆化)
// 之前:需要手动 useMemo / useCallback / memo
function ExpensiveList({ items, onSelect }: Props) {
const sorted = useMemo(() => [...items].sort(), [items]);
const handleSelect = useCallback((id: string) => onSelect(id), [onSelect]);
return <List items={sorted} onSelect={handleSelect} />;
}
// ✅ 有 Compiler 后:直接写「朴素」代码,编译器自动插入记忆化
function ExpensiveList({ items, onSelect }: Props) {
const sorted = [...items].sort(); // 编译器会自动 memo 化
const handleSelect = (id: string) => onSelect(id);
return <List items={sorted} onSelect={handleSelect} />;
}
React Compiler 的定位与限制:
① 它是【编译时】工具(Babel/SWC 插件),在构建时分析组件并插入记忆化
② 它依赖「组件是纯函数」的假设 → 有副作用的组件会被跳过(并给出提示)
③ 它【不替代】「理解引用稳定」这些概念——因为:
· 跨组件的引用传递(父组件把函数传给子组件)依然需要注意
· 有编译器覆盖不到的情况(如手写的外部 store 订阅)
④ 目前是可选特性(需要配置构建工具),生产使用需评估
一句话:它让「默认性能」更好,但不改变「要理解渲染机制」这件事。
九、移除 / 废弃的 API
| 移除/废弃 | 替代 |
|---|---|
defaultProps(函数组件) |
默认参数 ({ x = 1 }) |
字符串 ref(ref="input") |
回调 ref / useRef |
propTypes |
TypeScript |
ReactDOM.render / hydrate |
createRoot / hydrateRoot |
forwardRef |
ref 作为 prop(但仍兼容) |
useFormState |
useActionState |
ReactDOM.findDOMNode |
ref |
test-utils 的 act |
React.act |
十、常见坑
// ① 把 ref 当普通 prop 但又写了 forwardRef(重复且无必要)
const Comp = forwardRef(function Comp({ ref }: Props) { ... }); // ⚠️ 混用
// ② 在客户端组件里只用「服务端才有的能力」(如直接访问数据库)
'use client';
async function Page() {
const data = await db.query(); // ❌ 客户端没有 db
}
// ③ "use client" 写在「很高的位置」→ 整个子树都变成客户端代码
'use client'; // ⚠️ 放在顶层布局文件 → 失去 RSC 收益
// ④ 用 useActionState 但在 action 里「不抛错也不返回」→ 状态不更新
// ⑤ 在 useFormStatus 的「同一个组件」里调用(必须在 form 的子组件里)
function Form() {
const { pending } = useFormStatus(); // ❌ 读到的是「外层的 form」
return <form action={fn}>...</form>;
}
// ⑥ 用 use(Promise) 但 Promise 在渲染中创建(每次渲染都是新 Promise)
const p = fetchData(); // ❌ 每次渲染都发起请求
const data = use(p); // 应该由「上层/缓存」提供稳定 Promise
// ⑦ 认为「有 Compiler 就不用管性能了」
// ⑧ 依赖 defaultProps(React 19 已移除,函数组件上会失效)
// ⑨ 元数据提升与「客户端渲染的 title 管理库」重复(会出现两套)
// ⑩ 忘记「Server Component 的 props 必须可序列化」(不能传函数/类实例)
// ⑪ 用 ref 清理函数但返回了「非函数」的值(会被当成清理函数调用)
// ⑫ 升级时忘记同步 @types/react 到 19
十一、面试延伸
- 「React 19 有哪些主要变化?」
三条主线:① 异步与表单——Actions 体系(<form action>、useActionState、useFormStatus、useOptimistic)把「提交 + loading + 错误 + 乐观更新」统一起来,配合 Server Actions 可以直接调用服务端逻辑;② 人体工学改进——ref作为普通 prop(不再必须forwardRef)、<Context>可直接作 Provider、<title>/<meta>自动提升到<head>、回调 ref 支持返回清理函数;③ 架构能力——Server Components 正式稳定、React Compiler 提供编译时自动记忆化,同时移除了defaultProps、字符串 ref、propTypes等历史 API。 -
「什么是 Actions?它解决了什么问题?」
Actions 是 React 19 对「表单提交 / 数据变更」的统一抽象。它解决了过去的三块重复代码:① 手动preventDefault+ 用FormData收集数据(现在写成<form action={fn}>,React 自动处理);② 手动管理loading(现在用useActionState的isPending或子组件里的useFormStatus);③ 手动管理结果与错误、以及乐观更新(现在分别由useActionState的state和useOptimistic处理,失败自动回滚)。 -
「
use和useContext有什么区别?」
两个差异:①use(Context)可以在条件语句与循环里调用(而useContext必须遵守 Hook 的顶层调用规则);②use还能读取 Promise(配合Suspense实现数据获取)。注意:use仍然只能在渲染期间调用(不能放在事件处理或 Effect 里),并且「在渲染中创建 Promise」是错误用法(每次渲染都会新建)。 -
「React 19 里的
ref有什么变化?」
两个:① 函数组件可以直接把ref当普通 prop 接收(function Comp({ ref }) {}),不再必须用forwardRef(forwardRef仍兼容);② 回调 ref 可以返回清理函数(ref={(el) => { ...; return () => {...}; }}),让「订阅与释放」写在一起,比过去「用null再次调用回调」的方式更自然。 -
「Server Components 和 Client Components 的区别?」
Server Component(默认)在服务端执行:可以是async、能直接访问数据库与文件系统、代码不会进入客户端包;但不能有状态、事件处理器、浏览器 API(useState、onClick、window都不可用)。Client Component 用"use client"标记,可以有交互与状态。关键规则:"use client"标记的是一个「边界」——该文件及其所有导入的模块都会变成客户端代码,所以应当把边界尽量下沉到叶子组件。 -
「什么是 React Compiler?它会替代
useMemo吗?」
React Compiler 是一个编译时工具(Babel/SWC 插件),会在构建阶段分析组件并自动插入记忆化(相当于自动帮你写useMemo/useCallback/memo),因此日常代码可以写得更「朴素」。但它不能完全替代手动优化:① 它依赖「组件是纯函数」的假设(有副作用的组件会被跳过);② 跨组件的引用传递仍需注意;③ 有它覆盖不到的场景(如手写的外部 store 订阅)。结论是「默认性能更好,但仍需理解渲染机制」。 -
「Server Components 有什么好处?」
四点:① 减少客户端包体积——服务端组件的代码不会打包到客户端(如「只用一次的大依赖」「数据库 SDK」);② 更快的数据获取——服务端可以直接访问数据库,省掉一层 HTTP 往返,也避免「客户端瀑布式请求」;③ 更好的安全性——敏感逻辑与凭证留在服务端;④ 与静态/流式渲染配合更好——可先发送静态 shell,慢的部分用 Suspense 流式补充。 -
「React 19 升级要注意什么?」
五点:① 移除的历史 API——defaultProps(函数组件)、字符串ref、propTypes、useFormState(改useActionState),需要改代码;② 类型包升级到@types/react@19(尤其ref作为 prop 的类型推导);③ 客户端渲染项目基本无感(Actions/use都能用),但如果用了 RSC 框架要注意"use client"的边界划分;④ 第三方库兼容(表单库、UI 库可能需要新版本);⑤ Compiler 是可选特性——启用前先在切片上验证,注意「有副作用的组件会被跳过」并出现 lint 提示。
一句话速记
React 19 三大主线:异步与表单(Actions:
<form action>、useActionState、useFormStatus、useOptimistic,配合 Server Actions)、人体工学改进(ref作为普通 prop(forwardRef不再必需)、回调 ref 可返回清理函数、<Context>直接作 Provider、<title>/<meta>自动提升到<head>)、架构能力(Server Components 稳定、React Compiler 自动记忆化);新增use(可读 Promise 与 Context,可在条件/循环里用,但仍须在渲染期间);移除了defaultProps/ 字符串 ref /propTypes/useFormState;RSC 的边界规则是「"use client"会把它导入的所有模块都变成客户端代码」→ 边界要下沉。



最新评论
读过书不知道欧·亨利的人少。教科书上选文有
这小生活不错呀
不错,必须顶一下!
看着你还在坚持,很好
看来忙了也没时间更新博客了
NIce。学习了。。。。
网站不错!!!!
简洁实用,好文章!