React 19 新 Hooks
React 19 (2024.12) 引入的新 Hooks,2025 面试必考
1. use() Hook 🔥
1.1 核心概念
use() 是 React 19 最重要的新 Hook,统一了异步资源的读取方式。
jsx
import { use } from 'react';
function Comments({ commentsPromise }) {
// 直接读取 Promise!
const comments = use(commentsPromise);
return comments.map(c => <Comment key={c.id} {...c} />);
}1.2 为什么需要 use()?
传统方式的问题:
jsx
// ❌ 传统方式:useEffect + useState
function Comments({ id }) {
const [comments, setComments] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
setLoading(true);
fetchComments(id)
.then(setComments)
.catch(setError)
.finally(() => setLoading(false));
}, [id]);
if (loading) return <Spinner />;
if (error) return <Error />;
return <CommentList comments={comments} />;
}use() 的优势:
jsx
// ✅ React 19:use() + Suspense
function Comments({ commentsPromise }) {
const comments = use(commentsPromise);
return <CommentList comments={comments} />;
}
// 父组件处理 loading 状态
<Suspense fallback={<Spinner />}>
<Comments commentsPromise={fetchComments(id)} />
</Suspense>1.3 use() 的特殊之处
可以在条件语句中使用(唯一可以的 Hook):
jsx
function User({ userPromise, shouldFetch }) {
// ✅ use() 可以在条件中调用
if (shouldFetch) {
const user = use(userPromise);
return <Profile user={user} />;
}
return <Guest />;
}1.4 use() 读取 Context
jsx
// use() 也可以读取 Context
function ThemeButton() {
// 等同于 useContext(ThemeContext)
const theme = use(ThemeContext);
// 但可以在条件中使用
if (someCondition) {
const theme = use(ThemeContext);
}
return <button className={theme}>Click</button>;
}1.5 工作原理
┌─────────────────────────────────────────────────┐
│ use() 工作流程 │
├─────────────────────────────────────────────────┤
│ │
│ use(promise) │
│ │ │
│ ├─ Promise pending ──→ 抛出 Promise │
│ │ (Suspense 捕获) │
│ │ │
│ ├─ Promise fulfilled ─→ 返回结果 │
│ │ │
│ └─ Promise rejected ──→ 抛出错误 │
│ (ErrorBoundary) │
│ │
└─────────────────────────────────────────────────┘2. useActionState 🔥
2.1 核心概念
管理表单提交动作的状态,替代旧的 useFormState。
jsx
import { useActionState } from 'react';
function LoginForm() {
const [state, submitAction, isPending] = useActionState(
async (previousState, formData) => {
const email = formData.get('email');
const password = formData.get('password');
try {
await login(email, password);
return { success: true };
} catch (error) {
return { error: error.message };
}
},
{ error: null, success: false } // 初始状态
);
return (
<form action={submitAction}>
<input name="email" type="email" />
<input name="password" type="password" />
{state.error && <p className="error">{state.error}</p>}
<button disabled={isPending}>
{isPending ? 'Logging in...' : 'Login'}
</button>
</form>
);
}2.2 API 说明
typescript
const [state, action, isPending] = useActionState(
actionFn, // (prevState, formData) => newState
initialState, // 初始状态
permalink? // 可选,用于 SSR
);
// 返回值:
// - state: 当前状态
// - action: 传给 form action 的函数
// - isPending: 是否正在执行2.3 与 Server Actions 配合
jsx
// actions.js
'use server';
export async function createPost(prevState, formData) {
const title = formData.get('title');
if (!title) {
return { error: 'Title is required' };
}
await db.posts.create({ title });
revalidatePath('/posts');
return { success: true };
}
// component.jsx
'use client';
import { createPost } from './actions';
function CreatePostForm() {
const [state, action, isPending] = useActionState(createPost, {});
return (
<form action={action}>
<input name="title" />
{state.error && <span>{state.error}</span>}
<button disabled={isPending}>Create</button>
</form>
);
}3. useFormStatus 🔥
3.1 核心概念
获取父级 form 的提交状态,必须在 <form> 内部的组件中使用。
jsx
import { useFormStatus } from 'react-dom';
function SubmitButton() {
const { pending, data, method, action } = useFormStatus();
return (
<button disabled={pending}>
{pending ? 'Submitting...' : 'Submit'}
</button>
);
}
function MyForm() {
return (
<form action={submitForm}>
<input name="name" />
<SubmitButton /> {/* 必须是 form 的子组件 */}
</form>
);
}3.2 API 说明
typescript
const status = useFormStatus();
// 返回对象:
{
pending: boolean, // 是否正在提交
data: FormData, // 提交的表单数据
method: string, // 提交方法 (GET/POST)
action: Function // 正在执行的 action
}3.3 注意事项
jsx
// ❌ 错误:不能在 form 外部使用
function WrongUsage() {
const status = useFormStatus(); // 报错!
return (
<form>
<button disabled={status.pending}>Submit</button>
</form>
);
}
// ✅ 正确:必须在 form 的子组件中
function SubmitButton() {
const status = useFormStatus();
return <button disabled={status.pending}>Submit</button>;
}
function CorrectUsage() {
return (
<form action={...}>
<SubmitButton />
</form>
);
}4. useOptimistic 🔥
4.1 核心概念
实现乐观更新,在服务端响应前先更新 UI,失败时自动回滚。
jsx
import { useOptimistic } from 'react';
function Messages({ messages, sendMessage }) {
const [optimisticMessages, addOptimisticMessage] = useOptimistic(
messages,
(state, newMessage) => [...state, { ...newMessage, sending: true }]
);
async function handleSubmit(formData) {
const text = formData.get('text');
// 立即显示(乐观更新)
addOptimisticMessage({ text, sending: true });
// 实际发送
await sendMessage(text);
}
return (
<>
{optimisticMessages.map((msg, i) => (
<div key={i} style={{ opacity: msg.sending ? 0.5 : 1 }}>
{msg.text}
</div>
))}
<form action={handleSubmit}>
<input name="text" />
<button>Send</button>
</form>
</>
);
}4.2 API 说明
typescript
const [optimisticState, addOptimistic] = useOptimistic(
state, // 真实状态
updateFn // (currentState, optimisticValue) => newState
);
// 工作流程:
// 1. 调用 addOptimistic(value) 立即更新 UI
// 2. 真实请求完成后,state 更新,optimisticState 自动同步
// 3. 如果请求失败,optimisticState 回滚到 state4.3 典型场景:点赞功能
jsx
function LikeButton({ postId, likes, isLiked }) {
const [optimisticLiked, setOptimisticLiked] = useOptimistic(isLiked);
const [optimisticLikes, setOptimisticLikes] = useOptimistic(likes);
async function handleLike() {
// 乐观更新
setOptimisticLiked(!optimisticLiked);
setOptimisticLikes(optimisticLiked ? likes - 1 : likes + 1);
// 实际请求
await toggleLike(postId);
}
return (
<button onClick={handleLike}>
{optimisticLiked ? '❤️' : '🤍'} {optimisticLikes}
</button>
);
}5. useFormState (已重命名)
⚠️ React 19 中
useFormState已重命名为useActionState
jsx
// ❌ 旧 API (React 18.3)
import { useFormState } from 'react-dom';
// ✅ 新 API (React 19)
import { useActionState } from 'react';6. ref 作为 prop(不再需要 forwardRef)
6.1 React 19 的简化
jsx
// ❌ React 18:需要 forwardRef
const Input = forwardRef((props, ref) => {
return <input ref={ref} {...props} />;
});
// ✅ React 19:ref 直接作为 prop
function Input({ ref, ...props }) {
return <input ref={ref} {...props} />;
}6.2 对比
jsx
// React 18
const Button = forwardRef(function Button({ children }, ref) {
return <button ref={ref}>{children}</button>;
});
// React 19
function Button({ children, ref }) {
return <button ref={ref}>{children}</button>;
}
// 使用方式相同
<Button ref={buttonRef}>Click</Button>7. 其他 React 19 改进
7.1 Context 作为 Provider
jsx
// ❌ React 18
<ThemeContext.Provider value={theme}>
{children}
</ThemeContext.Provider>
// ✅ React 19:Context 直接作为 Provider
<ThemeContext value={theme}>
{children}
</ThemeContext>7.2 文档元数据支持
jsx
// React 19:直接在组件中写 title、meta
function BlogPost({ post }) {
return (
<>
<title>{post.title}</title>
<meta name="description" content={post.summary} />
<article>{post.content}</article>
</>
);
}7.3 样式表支持
jsx
function Component() {
return (
<>
<link rel="stylesheet" href="styles.css" precedence="default" />
<div className="styled">Content</div>
</>
);
}8. 面试高频问题
Q1: use() 和 useEffect + useState 的区别?
| 对比项 | useEffect + useState | use() |
|---|---|---|
| 代码量 | 多(状态+加载+错误) | 少 |
| 加载状态 | 手动管理 | Suspense 处理 |
| 错误处理 | 手动 catch | ErrorBoundary |
| 数据获取时机 | 组件内部 | 可在组件外部 |
| 条件调用 | 不支持 | 支持 |
Q2: useActionState 和 useState + 手动处理的区别?
useActionState 优势:
- 自动管理 pending 状态
- 与 form action 原生集成
- 支持 Server Actions
- 支持渐进增强(JS 禁用时仍可工作)
Q3: useOptimistic 什么时候用?
适用场景:
- 点赞/收藏等高频交互
- 消息发送
- 评论提交
- 任何需要即时反馈但依赖服务端确认的操作
不适用场景:
- 关键业务操作(支付、删除)
- 需要立即获取服务端响应的场景
Q4: 为什么 useFormStatus 必须在 form 子组件中使用?
因为 useFormStatus 通过 React 内部机制查找最近的父级 <form> 元素的提交状态。如果不在 form 内部,找不到关联的 form,无法获取状态。
Q5: React 19 移除 forwardRef 的原因?
简化 API:
- forwardRef 增加了组件嵌套层级
- 学习成本高
- TypeScript 类型推断复杂
- ref 作为 prop 更直观、更统一