React 19 新特性
问题
React 19 有哪些新特性?Actions 是什么?use() 钩子怎么用?
答案
React 19 是一个重大版本更新,核心特性包括:Actions(数据变更)、新 Hooks(useActionState、useOptimistic、use)、Server Components 正式化、ref 作为 prop、文档元数据支持等。
核心特性概览
| 特性 | 说明 |
|---|---|
| React Compiler | 自动优化,减少手动 memo |
| Actions | 处理表单提交和数据变更的新范式 |
| useActionState | 管理 action 状态(替代 useFormState) |
| useFormStatus | 获取表单提交状态 |
| useOptimistic | 乐观更新 UI |
| use() | 读取 Promise 和 Context |
| ref 作为 prop | 无需 forwardRef |
| 文档元数据 | 组件内直接写 <title>、<meta> |
| 资源预加载 | preload、preinit API |
React Compiler
React Compiler(原名 React Forget)是 React 19 的编译时优化工具,自动为组件添加 memoization,减少手动使用 useMemo、useCallback、React.memo 的需要。
工作原理
// 你写的代码
function TodoList({ todos, filter }) {
const visibleTodos = todos.filter(todo =>
filter === 'all' || todo.status === filter
);
return (
<ul>
{visibleTodos.map(todo => (
<TodoItem key={todo.id} todo={todo} />
))}
</ul>
);
}
// Compiler 自动优化后(概念示意)
function TodoList({ todos, filter }) {
const visibleTodos = useMemo(
() => todos.filter(todo =>
filter === 'all' || todo.status === filter
),
[todos, filter]
);
return (
<ul>
{visibleTodos.map(todo => (
<MemoizedTodoItem key={todo.id} todo={todo} />
))}
</ul>
);
}
安装和配置
- npm
- Yarn
- pnpm
- Bun
npm install -D babel-plugin-react-compiler
npm install react@19 react-dom@19
yarn add --dev babel-plugin-react-compiler
yarn add react@19 react-dom@19
pnpm add -D babel-plugin-react-compiler
pnpm add react@19 react-dom@19
bun add --dev babel-plugin-react-compiler
bun add react@19 react-dom@19
// babel.config.js
module.exports = {
plugins: [
['babel-plugin-react-compiler', {
// 配置选项
}],
],
};
// vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [
react({
babel: {
plugins: ['babel-plugin-react-compiler'],
},
}),
],
});
编译规则
React Compiler 遵循 React 规则进行优化:
| 规则 | 说明 |
|---|---|
| 纯函数组件 | 相同输入产生相同输出 |
| Hooks 规则 | 顶层调用,不在条件中 |
| 不可变 props/state | 不直接修改 |
// ✅ Compiler 可优化
function Good({ items }) {
const sorted = [...items].sort((a, b) => a.id - b.id);
return <List items={sorted} />;
}
// ❌ Compiler 跳过(直接修改)
function Bad({ items }) {
items.sort((a, b) => a.id - b.id); // 直接修改 props
return <List items={items} />;
}
跳过编译
使用 "use no memo" 指令跳过特定组件:
function SpecialComponent() {
"use no memo"; // 跳过 Compiler 优化
// 有副作用或特殊需求的代码
return <div>...</div>;
}
React Compiler 可以与现有的 useMemo、useCallback 共存。已有的手动优化会被保留,Compiler 只优化未处理的部分。建议新项目使用 Compiler,逐步移除手动优化代码。
Actions
什么是 Actions?
Actions 是 React 19 处理数据变更的新范式。它将异步操作(如表单提交、数据更新)标准化。
表单 Actions
// React 19 之前
function Form() {
const [isPending, setIsPending] = useState(false);
const [error, setError] = useState<string | null>(null);
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setIsPending(true);
setError(null);
try {
const formData = new FormData(e.target as HTMLFormElement);
await submitForm(formData);
} catch (err) {
setError((err as Error).message);
} finally {
setIsPending(false);
}
};
return (
<form onSubmit={handleSubmit}>
<input name="email" />
<button disabled={isPending}>
{isPending ? 'Submitting...' : 'Submit'}
</button>
{error && <p>{error}</p>}
</form>
);
}
// React 19 使用 Actions
function Form() {
const [error, setError] = useState<string | null>(null);
async function submitAction(formData: FormData) {
setError(null);
try {
await submitForm(formData);
} catch (err) {
setError((err as Error).message);
}
}
return (
<form action={submitAction}>
<input name="email" />
<SubmitButton />
{error && <p>{error}</p>}
</form>
);
}
function SubmitButton() {
const { pending } = useFormStatus();
return (
<button disabled={pending}>
{pending ? 'Submitting...' : 'Submit'}
</button>
);
}
- 自动管理 pending 状态:无需手动
setIsPending - 渐进增强:支持无 JS 的表单提交
- 与 Suspense 集成:pending 时可显示 fallback
- 错误边界支持:可被 Error Boundary 捕获
useActionState
管理 Action 的状态,包括返回值、pending 状态和错误:
import { useActionState } from 'react';
interface FormState {
message: string;
success: boolean;
}
async function createUser(
prevState: FormState,
formData: FormData
): Promise<FormState> {
const name = formData.get('name') as string;
if (!name) {
return { message: '请输入姓名', success: false };
}
try {
await saveUser({ name });
return { message: '创建成功!', success: true };
} catch {
return { message: '创建失败', success: false };
}
}
function CreateUserForm() {
const [state, formAction, isPending] = useActionState(
createUser,
{ message: '', success: false }
);
return (
<form action={formAction}>
<input name="name" placeholder="姓名" />
<button disabled={isPending}>
{isPending ? '创建中...' : '创建用户'}
</button>
{state.message && (
<p style={{ color: state.success ? 'green' : 'red' }}>
{state.message}
</p>
)}
</form>
);
}
useActionState 签名
const [state, formAction, isPending] = useActionState(
action, // async (prevState, formData) => newState
initialState, // 初始状态
permalink? // 可选,SSR 时的 URL
);
| 返回值 | 说明 |
|---|---|
state | 当前状态(action 返回值) |
formAction | 传给 form 的 action |
isPending | 是否正在执行 |
useFormStatus
获取父级表单的提交状态:
import { useFormStatus } from 'react-dom';
function SubmitButton() {
const { pending, data, method, action } = useFormStatus();
return (
<button type="submit" disabled={pending}>
{pending ? '提交中...' : '提交'}
</button>
);
}
function Form() {
return (
<form action={submitAction}>
<input name="email" />
{/* SubmitButton 必须在 form 内部 */}
<SubmitButton />
</form>
);
}
useFormStatus 只能在 <form> 的子组件中使用,不能在同一个组件中使用。
// ❌ 错误:同一组件内
function Form() {
const { pending } = useFormStatus(); // 无法获取自身 form 状态
return <form action={action}>...</form>;
}
// ✅ 正确:子组件中
function Form() {
return (
<form action={action}>
<SubmitButton /> {/* useFormStatus 在这里 */}
</form>
);
}
useOptimistic
实现乐观更新,在异步操作完成前先更新 UI:
import { useOptimistic } from 'react';
interface Message {
id: string;
text: string;
sending?: boolean;
}
function ChatMessages({ messages }: { messages: Message[] }) {
const [optimisticMessages, addOptimisticMessage] = useOptimistic(
messages,
// 更新函数
(state: Message[], newMessage: Message) => [
...state,
{ ...newMessage, sending: true }
]
);
async function sendMessage(formData: FormData) {
const text = formData.get('message') as string;
const newMessage: Message = {
id: crypto.randomUUID(),
text,
};
// 乐观更新:立即显示
addOptimisticMessage(newMessage);
// 实际发送
await sendToServer(newMessage);
}
return (
<div>
{optimisticMessages.map(msg => (
<div key={msg.id} style={{ opacity: msg.sending ? 0.5 : 1 }}>
{msg.text}
{msg.sending && ' (发送中...)'}
</div>
))}
<form action={sendMessage}>
<input name="message" />
<button type="submit">发送</button>
</form>
</div>
);
}
use() 钩子
use() 是一个特殊的钩子,可以读取 Promise 和 Context:
读取 Promise
import { use, Suspense } from 'react';
// 创建 Promise(在组件外部)
const userPromise = fetchUser(userId);
function UserProfile() {
// use() 会等待 Promise resolve
const user = use(userPromise);
return <div>{user.name}</div>;
}
function App() {
return (
<Suspense fallback={<Loading />}>
<UserProfile />
</Suspense>
);
}
条件读取 Context
import { use } from 'react';
function Panel({ showTheme }: { showTheme: boolean }) {
// use() 可以在条件语句中调用
if (showTheme) {
const theme = use(ThemeContext);
return <div className={theme}>Themed Panel</div>;
}
return <div>Plain Panel</div>;
}
use() vs useContext
| 特性 | use() | useContext() |
|---|---|---|
| 条件调用 | ✅ 可以 | ❌ 不可以 |
| 循环中调用 | ✅ 可以 | ❌ 不可以 |
| 读取 Promise | ✅ 可以 | ❌ 不可以 |
| 读取 Context | ✅ 可以 | ✅ 可以 |
// ✅ use() 可以条件调用
function Component({ shouldFetch }: { shouldFetch: boolean }) {
if (shouldFetch) {
const data = use(dataPromise);
return <div>{data}</div>;
}
return <div>No data</div>;
}
// ❌ useContext 不能条件调用
function Component({ shouldUse }: { shouldUse: boolean }) {
// if (shouldUse) {
// const ctx = useContext(MyContext); // 违反 Hooks 规则
// }
}
ref 作为 prop
React 19 允许函数组件直接接收 ref 作为 prop,无需 forwardRef:
// React 18 需要 forwardRef
const Input = forwardRef<HTMLInputElement, InputProps>((props, ref) => {
return <input ref={ref} {...props} />;
});
// React 19 直接作为 prop
function Input({ ref, ...props }: { ref?: React.Ref<HTMLInputElement> }) {
return <input ref={ref} {...props} />;
}
// 使用
function Form() {
const inputRef = useRef<HTMLInputElement>(null);
return (
<form>
<Input ref={inputRef} placeholder="Enter text" />
<button onClick={() => inputRef.current?.focus()}>
Focus
</button>
</form>
);
}
forwardRef 在 React 19 中仍然有效,但已被标记为废弃。建议逐步迁移到新写法。
文档元数据
组件内直接渲染 <title>、<meta>、<link> 等,React 会自动提升到 <head>:
function BlogPost({ post }: { post: Post }) {
return (
<article>
{/* 自动提升到 <head> */}
<title>{post.title}</title>
<meta name="description" content={post.summary} />
<meta property="og:title" content={post.title} />
<link rel="canonical" href={`https://example.com/posts/${post.id}`} />
{/* 正常内容 */}
<h1>{post.title}</h1>
<p>{post.content}</p>
</article>
);
}
资源预加载 API
新增资源预加载函数:
import { prefetchDNS, preconnect, preload, preinit } from 'react-dom';
function App() {
// DNS 预解析
prefetchDNS('https://api.example.com');
// 预连接
preconnect('https://cdn.example.com');
// 预加载资源
preload('/fonts/roboto.woff2', { as: 'font', type: 'font/woff2' });
preload('/hero.jpg', { as: 'image' });
// 预初始化脚本/样式
preinit('/analytics.js', { as: 'script' });
preinit('/critical.css', { as: 'style' });
return <div>...</div>;
}
| API | 说明 | 生成标签 |
|---|---|---|
prefetchDNS | DNS 预解析 | <link rel="dns-prefetch"> |
preconnect | 预连接 | <link rel="preconnect"> |
preload | 预加载资源 | <link rel="preload"> |
preinit | 预初始化 | <link rel="modulepreload"> 或 <style> |
Server Actions
Server Actions 允许客户端组件调用服务端函数:
// actions.ts
'use server';
import { db } from './db';
import { revalidatePath } from 'next/cache';
export async function createPost(formData: FormData) {
const title = formData.get('title') as string;
const content = formData.get('content') as string;
await db.post.create({
data: { title, content }
});
revalidatePath('/posts');
}
export async function deletePost(id: string) {
await db.post.delete({ where: { id } });
revalidatePath('/posts');
}
// PostForm.tsx
'use client';
import { createPost, deletePost } from './actions';
function PostForm() {
return (
<form action={createPost}>
<input name="title" placeholder="标题" />
<textarea name="content" placeholder="内容" />
<button type="submit">发布</button>
</form>
);
}
function DeleteButton({ postId }: { postId: string }) {
const deleteWithId = deletePost.bind(null, postId);
return (
<form action={deleteWithId}>
<button type="submit">删除</button>
</form>
);
}
改进的错误处理
Hydration 错误信息
React 19 提供更详细的 hydration 错误信息:
Uncaught Error: Hydration failed because the server rendered HTML
didn't match the client.
- Server: <div class="container">
- Client: <div class="wrapper">
Tree:
<App>
<Layout>
<Header>
<div> ← 不匹配
ref 清理函数
function Component() {
return (
<input
ref={(element) => {
if (element) {
// 设置
element.focus();
}
// React 19:返回清理函数
return () => {
// 清理逻辑
console.log('ref 被清理');
};
}}
/>
);
}
其他改进
支持自定义元素
function App() {
return (
<my-custom-element
customAttribute="value"
onCustomEvent={(e) => console.log(e)}
/>
);
}
Context 作为 Provider
// React 18
<ThemeContext.Provider value={theme}>
{children}
</ThemeContext.Provider>
// React 19:直接使用 Context
<ThemeContext value={theme}>
{children}
</ThemeContext>
useDeferredValue 初始值
// React 19 支持初始值
const deferredValue = useDeferredValue(value, initialValue);
// 首次渲染使用 initialValue
// 后续渲染延迟更新 value
常见面试问题
Q1: React 19 的 Actions 是什么?解决了什么问题?
答案:
Actions 是 React 19 处理数据变更的新范式,解决了手动管理异步状态的繁琐:
之前的问题:
// 手动管理 pending、error 状态
const [isPending, setIsPending] = useState(false);
const [error, setError] = useState(null);
async function handleSubmit() {
setIsPending(true);
setError(null);
try {
await submit();
} catch (e) {
setError(e);
} finally {
setIsPending(false);
}
}
Actions 解决方案:
// 自动管理状态
const [state, formAction, isPending] = useActionState(submitAction, initialState);
<form action={formAction}>
<button disabled={isPending}>提交</button>
</form>
优势:
- 自动管理 pending 状态
- 与 Suspense、Error Boundary 集成
- 支持渐进增强
Q2: use() 和其他 Hooks 有什么区别?
答案:
use() 是一个特殊的钩子,打破了传统 Hooks 规则:
| 特性 | use() | 其他 Hooks |
|---|---|---|
| 条件调用 | ✅ 可以 | ❌ 不可以 |
| 循环中调用 | ✅ 可以 | ❌ 不可以 |
| 读取 Promise | ✅ 可以 | ❌ 不可以 |
| 在 try/catch 中 | ✅ 可以 | ❌ 不可以 |
function Component({ condition }: { condition: boolean }) {
// ✅ use() 可以条件调用
if (condition) {
const data = use(promise);
const theme = use(ThemeContext);
}
// ❌ useState 不能条件调用
// if (condition) {
// const [state, setState] = useState(0);
// }
}
Q3: useOptimistic 的使用场景是什么?
答案:
useOptimistic 用于乐观更新,在异步操作完成前立即更新 UI,提升用户体验。
适用场景:
- 点赞/收藏
- 发送消息
- 添加评论
- 购物车操作
const [optimisticLikes, addOptimisticLike] = useOptimistic(
likes,
(state, newLike) => state + 1
);
async function handleLike() {
addOptimisticLike(1); // 立即 +1
await likePost(postId); // 等待服务器
// 成功:保持状态
// 失败:自动回滚
}
Q4: React 19 的 ref 有什么变化?
答案:
1. ref 作为普通 prop:无需 forwardRef
// React 19
function Input({ ref, ...props }) {
return <input ref={ref} {...props} />;
}
// 直接传递
<Input ref={inputRef} />
2. ref 支持清理函数:
<div ref={(element) => {
// 设置逻辑
element?.focus();
// 返回清理函数
return () => {
console.log('清理');
};
}} />
Q5: React 19 相比 React 18 有哪些重要变化?
答案:
| 特性 | React 18 | React 19 |
|---|---|---|
| 自动优化 | 手动 memo | React Compiler |
| 表单处理 | 手动管理状态 | Actions + useActionState |
| 乐观更新 | 手动实现 | useOptimistic |
| 读取 Promise | 需要库支持 | use() |
| ref 传递 | forwardRef | 直接作为 prop |
| 文档元数据 | react-helmet | 内置支持 |
| Context Provider | Context.Provider | 直接 <Context> |
| Server Actions | 框架实现 | 标准化 |
升级建议:
- 尝试引入 React Compiler 自动优化
- 将表单迁移到 Actions
- 移除 forwardRef,改用 ref prop
- 使用内置文档元数据替代 react-helmet
- 考虑使用 useOptimistic 优化交互
Q6: React Compiler 是什么?解决了什么问题?
答案:
React Compiler 是编译时优化工具,自动添加 memoization,解决了手动优化的痛点:
之前的问题:
// 需要手动优化
const memoizedValue = useMemo(() => compute(a, b), [a, b]);
const memoizedFn = useCallback(() => handle(x), [x]);
const MemoComponent = React.memo(Component);
React Compiler 解决方案:
// 正常写代码,Compiler 自动优化
const value = compute(a, b);
const fn = () => handle(x);
优势:
- 减少心智负担,无需手动分析依赖
- 避免依赖数组错误
- 代码更简洁,可读性更高
- 编译器比人更擅长全局优化