跳到主要内容

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,减少手动使用 useMemouseCallbackReact.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 install -D babel-plugin-react-compiler
npm install 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 可以与现有的 useMemouseCallback 共存。已有的手动优化会被保留,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>
);
}
Actions 的优势
  1. 自动管理 pending 状态:无需手动 setIsPending
  2. 渐进增强:支持无 JS 的表单提交
  3. 与 Suspense 集成:pending 时可显示 fallback
  4. 错误边界支持:可被 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() 是一个特殊的钩子,可以读取 PromiseContext

读取 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说明生成标签
prefetchDNSDNS 预解析<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 18React 19
自动优化手动 memoReact Compiler
表单处理手动管理状态Actions + useActionState
乐观更新手动实现useOptimistic
读取 Promise需要库支持use()
ref 传递forwardRef直接作为 prop
文档元数据react-helmet内置支持
Context ProviderContext.Provider直接 <Context>
Server Actions框架实现标准化

升级建议

  1. 尝试引入 React Compiler 自动优化
  2. 将表单迁移到 Actions
  3. 移除 forwardRef,改用 ref prop
  4. 使用内置文档元数据替代 react-helmet
  5. 考虑使用 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);

优势

  • 减少心智负担,无需手动分析依赖
  • 避免依赖数组错误
  • 代码更简洁,可读性更高
  • 编译器比人更擅长全局优化

相关链接