Loading...
Loading...
Compare original and translation side by side
| Feature | Description |
|---|---|
| Actions | Async functions in transitions for data mutations |
| Handle action state with pending/error |
| Access parent form status |
| Optimistic UI updates |
| Read resources in render (Promises, Context) |
| Server Components | Components that render on server |
| Server Actions | Server functions called from client |
| React Compiler | Automatic memoization |
| Ref as prop | Pass ref directly without forwardRef |
| 特性 | 描述 |
|---|---|
| Actions | 用于数据变更的过渡态异步函数 |
| 处理包含等待/错误状态的动作状态 |
| 访问父表单状态 |
| 乐观UI更新 |
| 在渲染中读取资源(Promises、Context) |
| Server Components | 在服务器端渲染的组件 |
| Server Actions | 从客户端调用的服务器端函数 |
| React Compiler | 自动记忆化处理 |
| Ref as prop | 直接传递ref,无需forwardRef |
import { useTransition } from 'react';
function UpdateProfile() {
const [isPending, startTransition] = useTransition();
async function handleSubmit(formData: FormData) {
startTransition(async () => {
const result = await updateProfile(formData);
if (result.error) {
showToast(result.error);
}
});
}
return (
<form action={handleSubmit}>
<input name="name" type="text" />
<button type="submit" disabled={isPending}>
{isPending ? 'Saving...' : 'Save'}
</button>
</form>
);
}import { useTransition } from 'react';
function UpdateProfile() {
const [isPending, startTransition] = useTransition();
async function handleSubmit(formData: FormData) {
startTransition(async () => {
const result = await updateProfile(formData);
if (result.error) {
showToast(result.error);
}
});
}
return (
<form action={handleSubmit}>
<input name="name" type="text" />
<button type="submit" disabled={isPending}>
{isPending ? '保存中...' : '保存'}
</button>
</form>
);
}useFormStateimport { useActionState } from 'react';
interface FormState {
message: string | null;
errors: Record<string, string[]>;
}
async function createPost(prevState: FormState, formData: FormData): Promise<FormState> {
const title = formData.get('title') as string;
if (!title || title.length < 3) {
return { message: null, errors: { title: ['Title must be at least 3 characters'] } };
}
try {
await savePost({ title });
return { message: 'Post created!', errors: {} };
} catch (error) {
return { message: 'Failed to create post', errors: {} };
}
}
function CreatePostForm() {
const [state, formAction, isPending] = useActionState(createPost, {
message: null,
errors: {}
});
return (
<form action={formAction}>
<input name="title" type="text" />
{state.errors.title && <p className="error">{state.errors.title[0]}</p>}
<button type="submit" disabled={isPending}>
{isPending ? 'Creating...' : 'Create Post'}
</button>
</form>
);
}useFormStateimport { useActionState } from 'react';
interface FormState {
message: string | null;
errors: Record<string, string[]>;
}
async function createPost(prevState: FormState, formData: FormData): Promise<FormState> {
const title = formData.get('title') as string;
if (!title || title.length < 3) {
return { message: null, errors: { title: ['标题长度至少为3个字符'] } };
}
try {
await savePost({ title });
return { message: '帖子创建成功!', errors: {} };
} catch (error) {
return { message: '帖子创建失败', errors: {} };
}
}
function CreatePostForm() {
const [state, formAction, isPending] = useActionState(createPost, {
message: null,
errors: {}
});
return (
<form action={formAction}>
<input name="title" type="text" />
{state.errors.title && <p className="error">{state.errors.title[0]}</p>}
<button type="submit" disabled={isPending}>
{isPending ? '创建中...' : '创建帖子'}
</button>
</form>
);
}<form>import { useFormStatus } from 'react-dom';
function SubmitButton() {
const { pending, data, method, action } = useFormStatus();
return (
<button type="submit" disabled={pending}>
{pending ? 'Submitting...' : 'Submit'}
</button>
);
}
// Usage - must be child of <form>
function ContactForm() {
return (
<form action={submitContact}>
<input name="email" type="email" required />
<SubmitButton />
</form>
);
}<form>import { useFormStatus } from 'react-dom';
function SubmitButton() {
const { pending, data, method, action } = useFormStatus();
return (
<button type="submit" disabled={pending}>
{pending ? '提交中...' : '提交'}
</button>
);
}
// 使用方式 - 必须是<form>的子组件
function ContactForm() {
return (
<form action={submitContact}>
<input name="email" type="email" required />
<SubmitButton />
</form>
);
}'use client';
import { useFormStatus } from 'react-dom';
export function FormButton({ children, pendingText = 'Submitting...', ...props }) {
const { pending } = useFormStatus();
return (
<button {...props} disabled={pending || props.disabled}>
{pending ? pendingText : children}
</button>
);
}Full Reference: See hooks.md for useOptimistic, use() hook, Ref handling, Document Metadata.
'use client';
import { useFormStatus } from 'react-dom';
export function FormButton({ children, pendingText = '提交中...', ...props }) {
const { pending } = useFormStatus();
return (
<button {...props} disabled={pending || props.disabled}>
{pending ? pendingText : children}
</button>
);
}完整参考:查看hooks.md获取useOptimistic、use() hook、Ref处理、文档元数据的相关内容。
// app/users/page.tsx (Server Component by default)
export default async function UsersPage() {
const users = await fetchUsers();
return <UserList users={users} />;
}
// app/actions.ts
'use server';
export async function createPost(formData: FormData) {
const title = formData.get('title') as string;
await db.post.create({ data: { title } });
revalidatePath('/posts');
redirect(`/posts`);
}Full Reference: See server.md for Server Components patterns, React Compiler, Migration Guide.
// app/users/page.tsx(默认是Server Component)
export default async function UsersPage() {
const users = await fetchUsers();
return <UserList users={users} />;
}
// app/actions.ts
'use server';
export async function createPost(formData: FormData) {
const title = formData.get('title') as string;
await db.post.create({ data: { title } });
revalidatePath('/posts');
redirect(`/posts`);
}完整参考:查看server.md获取Server Components模式、React Compiler、迁移指南的相关内容。
useOptimisticuse()use()useOptimisticuse()use()reactreact-formsreact-hook-formreact-performancereactreact-formsreact-hook-formreact-performance| Anti-Pattern | Problem | Solution |
|---|---|---|
| Creating Promises in render for use() | New promise every render | Create promise outside component |
| Using useFormState instead of useActionState | Deprecated in React 19 | Use useActionState with isPending |
| Missing 'use server' directive | Code runs on client | Add 'use server' at top of file |
| Not validating Server Action inputs | Security vulnerability | Always validate with Zod |
| 反模式 | 问题 | 解决方案 |
|---|---|---|
| 在渲染中为use()创建Promises | 每次渲染都会生成新的Promise | 在组件外部创建Promise |
| 使用useFormState而非useActionState | React 19中已废弃 | 使用带isPending的useActionState |
| 缺少'use server'指令 | 代码在客户端运行 | 在文件顶部添加'use server' |
| 未验证Server Action输入 | 安全漏洞 | 始终使用Zod进行验证 |
| Issue | Likely Cause | Fix |
|---|---|---|
| Action not working | Missing action attribute | Add action={serverAction} to form |
| isPending not available | Using old useFormState | Switch to useActionState |
| Serialization errors | Passing functions/classes | Only pass serializable data |
| ref not working | Using old forwardRef pattern | Pass ref as regular prop |
| 问题 | 可能原因 | 修复方案 |
|---|---|---|
| Action无法工作 | 缺少action属性 | 为form添加action={serverAction} |
| isPending不可用 | 使用了旧版useFormState | 切换为useActionState |
| 序列化错误 | 传递了函数/类 | 仅传递可序列化的数据 |
| ref无法工作 | 使用了旧版forwardRef模式 | 将ref作为常规属性传递 |
| File | Content |
|---|---|
| hooks.md | useOptimistic, use(), Ref handling, Metadata |
| server.md | Server Components, Server Actions, Compiler, Migration |
| 文件 | 内容 |
|---|---|
| hooks.md | useOptimistic、use()、Ref处理、元数据 |
| server.md | Server Components模式、React Compiler、迁移指南 |