本文是基于 react19 的表单教学
文章目录
- 传统表单
- [React 19 Action 表单](#React 19 Action 表单)
-
- [基础用法 - 使用 action 属性](#基础用法 - 使用 action 属性)
- 表单重置
- [使用 useActionState 管理状态](#使用 useActionState 管理状态)
- [使用 useFormStatus 获取表单状态](#使用 useFormStatus 获取表单状态)
- [useFormStatus 和useActionState](#useFormStatus 和useActionState)
- 表单防重复提交
- [乐观更新(Optimistic Updates)](#乐观更新(Optimistic Updates))
- [客户端 Action 和 服务端 Action](#客户端 Action 和 服务端 Action)
传统表单
在 React 18 及之前,处理表单通常需要手动管理 useState、onChange、加载状态 (isPending)、错误处理,并编写 onSubmit 函数来阻止默认事件和发起异步请求。
js
// 传统方式 - 手动管理一切
import { useState } from "react"
export const TraditionalForm = () => {
// 1. 需要手动管理每个字段的状态
const [formData, setFormData] = useState<{ name: string; email: string }>({
name: '',
email: ''
});
// 2. 需要手动管理加载状态
const [loading, setLoading] = useState<boolean>(false);
// 3. 需要手动管理错误
const [error, setError] = useState(null);
// 4. 手动处理输入变化
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const { name, value } = e.target;
setFormData(prev => ({
...prev,
[name]: value
}));
};
// 5. 手动处理提交
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
// 必须阻止默认行为
e.preventDefault();
setLoading(true);
setError(null);
console.log('提交的表单数据:', formData);
try {
// 模拟异步操作,例如发送请求
setTimeout(() => {
setLoading(false);
console.log('表单提交成功!');
}, 2000);
} catch (error) {
setError(error.message);
} finally {
setLoading(false);
}
}
// 手动重置表单数据
const reset = () => {
setFormData({ name: '', email: '' });
setError(null);
}
return (
<div>
<h2>传统表单</h2>
<form onSubmit={handleSubmit}>
<div>
<label htmlFor="name">姓名:</label>
{/* 通过 onChange 事件更新 formData 的 name 属性 */}
<input type="text" id="name" name="name" onChange={handleChange} />
</div>
<div>
<label htmlFor="email">邮箱:</label>
{/* 通过 onChange 事件更新 formData 的 email 属性 */}
<input type="email" id="email" name="email" onChange={handleChange} required />
</div>
{error && <div style={{color: 'red'}}>{error}</div>}
<button disabled={loading} type="submit">{loading ? '提交中...' : '提交'}</button>
<button type="reset" onClick={reset}>重置</button>
</form>
</div>
)
}
React 19 Action 表单
Actions 是使用异步转换的函数。你可以直接将异步函数传递给 的 action 属性,React 会自动管理表单提交的整个生命周期。
工作原理:
- Action 函数接收 FormData 对象作为参数
- 支持同步和异步函数- 表单提交时自动处理加载状态
基础用法 - 使用 action 属性
js
// 模拟一个 POST 请求,实际项目中可以替换为 fetch 或 axios 等库
function mockPostRequest(formData: FormData): Promise<{ success: boolean }> {
return new Promise((resolve) => {
setTimeout(() => {
console.log('模拟发送请求,表单数据:', Object.fromEntries(formData.entries()));
resolve({ success: true });
}, 2000);
});
}
export const ActionFrom = () => {
const submitForm = async (formData: FormData) => {
try {
// 直接提交,无需手动管理 loading 状态
const response = await mockPostRequest(formData);
if (response.success) {
console.log('表单提交成功!');
// 成功后可重置表单
} else {
console.error('表单提交失败!');
}
} catch (error) {
console.error('提交表单时发生错误:', error);
}
}
return (
<div>
<h2>ActionFrom</h2>
{/* 直接使用 action 属性,不需要 onSubmit 和 preventDefault */}
<form action={submitForm}>
<div>
<label htmlFor="name">姓名:</label>
<input name="name" placeholder="姓名" />
</div>
<div>
<label htmlFor="email">邮箱:</label>
<input name="email" placeholder="邮箱" />
</div>
<button type="submit">提交</button>
</form>
</div>
)
}
表单重置
- 自动重置(推荐)
这是 React 19 的默认行为。当你给 form> 的 action 传入一个异步函数,并且该函数成功执行完成(没有抛出错误)后,React 会自动将表单内所有不受控组件(即没有显式设置 value,但有 name 属性的组件)的值重置为其初始状态 。
js
// 自动重置:当 updateName 成功执行后,表单会自动清空
function MyForm() {
const submitAction = async (formData) => {
const name = formData.get('name');
try {
await updateName(name); // 如果请求成功,React 19 会自动重置表单
alert('提交成功,表单已自动重置');
} catch (error) {
console.error('提交失败,表单保留原数据', error);
}
};
return (
<form action={submitAction}>
<input type="text" name="name" />
<button type="submit">提交</button>
</form>
);
}
- 手动重置
如果你需要在特定时机手动重置表单(例如点击一个独立的"清空"按钮),可以使用新的 React DOM API requestFormReset 。
js
import { requestFormReset } from 'react-dom';
function MyForm() {
const formRef = useRef(null);
const handleResetClick = () => {
if (formRef.current) {
requestFormReset(formRef.current); // 手动清空表单
}
};
return (
<form ref={formRef} action={submitAction}>
{/* 表单内容 */}
<button type="button" onClick={handleResetClick}>手动重置</button>
</form>
);
}
使用 useActionState 管理状态
useActionState 是 React 19 专门为简化表单和异步操作状态管理而推出的一个新 Hook。它可以帮你把之前分散在各个 useState 里的加载状态、错误信息和提交结果都整合起来,大大减少了样板代码。
侧重于表单提交结果的状态
基本语法
const [state, formAction, isPending] = useActionState(fn, initialState, permalink)
参数详解 : useActionState(fn, initialState, permalink)
- fn:必填 (previousState, payload) => newState ;当表单提交时执行的函数(通常是异步的)。它的第一个参数会自动接收上一次调用后的状态,第二个参数是本次的表单数据(FormData 对象)
- initialState: 必填;state 的初始值,可以是任何可序列化的值(如 null、{}、"")
- permalink:选填;用于渐进式增强:如果在JS加载前就提交了表单,浏览器会导航到这个指定的URL,而不是当前页面的URL
返回值详解 : state, formAction, isPending
- state :当前的状态值。首次渲染时等于你传入的 initialState,表单提交后会更新为 fn 函数返回的值
- formAction:新的 action 函数。你需要把它赋给 标签的 action 属性
- isPending:React 19 新增。表示表单是否正在提交中(pending),非常适合用来禁用按钮或显示加载动画,提升用户体验
使用场景
这个hook 非常适合处理那些需要根据后端返回结果来展示错误信息的表单,比如检查用户名是否已被占用、密码格式是否正确等。它让你能在一个地方统一管理提交逻辑和错误反馈。
js
import { useActionState } from 'react';
// 模拟一个 POST 请求,实际项目中可以替换为 fetch 或 axios 等库
function mockPostRequest(formData: FormData): Promise<{ success: boolean }> {
return new Promise((resolve) => {
setTimeout(() => {
console.log('模拟发送请求,表单数据:', Object.fromEntries(formData.entries()));
resolve({ success: true });
}, 2000);
});
}
// 定义 state 的类型
interface FormState {
success: boolean | null;
message: string;
errors: Record<string, string>;
}
export const UseActionStateForm = () => {
const submitForm = async (prevState: FormState, formData: FormData) : Promise<FormState> => {
// prevState: 上一次的状态
// formData: 表单数据
console.log('上一次的状态:', prevState);
try {
const response = await mockPostRequest(formData);
if (response.success) {
console.log('表单提交成功!');
// 成功后可重置表单
} else {
console.error('表单提交失败!');
}
// 返回新的状态,useActionState 会自动更新 state
return {
success: true,
message: '提交成功!',
errors: {},
};
} catch (error) {
console.error('提交表单时发生错误:', error);
// 返回错误状态
return {
success: false,
message: error instanceof Error ? error.message : '提交失败',
errors: {},
};
}
}
// // useActionState 返回 [state, action, isPending]
const [state, formAction, isPending] = useActionState(
submitForm,
{ success: null, message: '', errors: {} } // 返回值 state 的初始值
);
return (
<div>
<h2>UseActionStateForm</h2>
<form action={formAction}>
<div>
<label htmlFor="name">姓名:</label>
<input name="name" placeholder="姓名" />
</div>
<div>
<label htmlFor="email">邮箱:</label>
<input name="email" placeholder="邮箱" />
</div>
<button type="submit" disabled={isPending}>
{/* 显示提交状态 使用 isPending */}
{isPending ? '提交中...' : '提交'}
</button>
<div>
{/* 显示返回的消息 使用 state.message */}
{state.message && <p>{state.message}</p>}
</div>
</form>
</div>
)
}
表单验证
使用 useActionState 整合验证和提交的流程。
注意:useActionState 返回的 state 类型由你决定,可以是字符串、对象等,初始值通过第二个参数传入 。
由于使用了 action 提交后验证失败会充值表单,无法保存填写过的数据,所以我们需要使用 useState 来保存表单数据
js
import { useActionState, useState } from 'react';
// 模拟一个 POST 请求
function mockPostRequest(formData: FormData): Promise<{ success: boolean }> {
return new Promise((resolve) => {
setTimeout(() => {
console.log('模拟发送请求,表单数据:', Object.fromEntries(formData.entries()));
resolve({ success: true });
}, 2000);
});
}
// 定义 state 的类型
interface FormState {
success: boolean | null;
message: string;
errors: Record<string, string>;
}
export const UseActionStateForm = () => {
// 使用 useState 来管理表单状态,初始值为 null 或者一个默认状态对象,防止验证失败重置
const [formData, setFormData] = useState({
name: '',
email: ''
});
// 注意:这里使用 action 函数的形式
const submitForm = async (
prevState: FormState | null,
formData: FormData
): Promise<FormState | null> => {
console.log('上一次的状态:', prevState);
const name = formData.get('name') as string;
const email = formData.get('email') as string;
// 简单的前端验证,返回的状态(验证结果)会自动更新到组件中
if (!name || !email) {
return {
success: false,
message: '请填写所有字段',
errors: {
name: !name ? '姓名不能为空' : '',
email: !email ? '邮箱不能为空' : '',
},
};
}
try {
await mockPostRequest(formData);
console.log('表单提交成功!');
return {
success: true,
message: '提交成功',
errors: {}
}; // 成功,返回成功状态
} catch (error) {
console.error('提交表单时发生错误:', error);
// 返回错误状态
return {
success: false,
message: '提交失败',
errors: {}
};
}
};
// 使用 useActionState 来获取当前状态、提交函数和加载状态
const [state, formAction, isPending] = useActionState(
submitForm,
null as FormState | null
);
// 处理输入变化,更新本地状态(可选)
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const { name, value } = e.target;
setFormData(prev => ({
...prev,
[name]: value
}));
}
return (
<div>
<h2>UseActionStateForm</h2>
<form action={formAction}>
<div>
<label htmlFor="name">姓名:</label>
<input name="name" placeholder="姓名" value={formData.name}
onChange={handleInputChange} />
</div>
<div>
<label htmlFor="email">邮箱:</label>
<input name="email" placeholder="邮箱" value={formData.email}
onChange={handleInputChange} />
</div>
<button type="submit" disabled={isPending}>
{isPending ? '提交中...' : '提交'}
</button>
<div>
{/* 显示返回状态------验证错误信息 */}
{state?.errors?.name && (
<p style={{ color: 'red' }}>{state.errors.name}</p>
)}
{state?.errors?.email && (
<p style={{ color: 'red' }}>{state.errors.email}</p>
)}
{/* 显示返回状态------通用消息 */}
{state?.message && (
<p style={{
color: state.success ? 'green' : 'red',
marginTop: '10px'
}}>
{state.message}
</p>
)}
</div>
</form>
</div>
);
};
使用 useFormStatus 获取表单状态
useFormStatus 是 React 19 提供的一个新 Hook,用于获取表单提交时的状态信息。它可以帮助你实现提交时禁用按钮、显示加载状态、展示正在提交的数据等交互。
侧重于表单提交过程的信息
返回值
它不接收任何参数,返回一个包含以下四个属性的对象:
| 属性 | 类型 | 说明 |
|---|---|---|
| pending | boolean | true 表示表单正在提交中,false 表示空闲 |
| data | FormData 对象或 null | 包含父级 正在提交的数据。若没有正在进行的提交,则为 null。可以通过 data.get(fieldName) 获取具体字段值 |
| method | string | 表单的提交方法,值为 'get' 或 'post',对应 标签的 method 属性 |
| action | function 或 null | 指向父级 的 action 属性的函数引用。如果 action 是一个 URL 字符串或未指定,则该值为 null。 |
使用规则
- 在
<form>表单里面使用。 - 需要配合 action 属性使用
- 只获取离他最近的父级 的状态。
- useFormStatus 必须在
<form>的子组件中调用 - useFormStatus 是从 'react-dom' 包中导出的,而不是 'react'。
import { useFormStatus } from 'react-dom';
子组件
js
// statusMessage.tsx
import { useFormStatus } from 'react-dom';
export const StatusMessage = () => {
const { pending, data } = useFormStatus();
if (pending && data) {
// 通过 data.get('字段名') 读取正在提交的数据
const username: string = data.get('username') as string;
return <p>正在为 <strong>{username}</strong> 提交申请...</p>;
}
return <p>请填写并提交表单。</p>;
}
父组件
js
import { StatusMessage } from './statusMessage';
export const useFormStatusForm = () => {
const formAction = async (formData: FormData) => {
console.log('提交的表单数据:', Object.fromEntries(formData.entries()));
// 模拟异步操作,例如发送请求
await new Promise((resolve) => {
setTimeout(() => {
console.log('表单提交成功!');
resolve(null);
}, 2000);
});
}
return (
<div>
<h2>UseFormStatusForm</h2>
{/* useFormStatus 要配合 action 使用 */}
<form action={formAction}>
<div>
<label htmlFor="name">姓名:</label>
<input name="username" type="text" />
</div>
<button type="submit">
提交
</button>
{/* 在表单里使用,只能获取最近父级form的状态 */}
<StatusMessage /> {/* 状态消息会随提交过程更新 */}
</form>
</div>
)
}
useFormStatus 和useActionState
useFormStatus 和 useActionState 都是 React 19 中与表单相关的 Hook,但它们的关注点和使用场景完全不同。
简单来说:
useFormStatus:关注"提交过程的状态"(正在提交吗?提交的是什么数据?)useActionState:关注"提交结果的状态"(提交成功了吗?返回了什么数据?有没有错误?)
不同点
- 关注点不同
useFormStatus - 关注"过程"
js
// 问:表单正在提交吗?提交的用户名是什么?
function SubmitButton() {
const { pending, data } = useFormStatus();
const username = data?.get('username');
return (
<button disabled={pending}>
{pending ? `正在为 ${username} 提交...` : '提交'}
</button>
);
}
useActionState - 关注"结果"
js
// 问:上次提交成功了吗?服务器返回了什么消息?
function MyForm() {
const [state, formAction, isPending] = useActionState(
async (prevState, formData) => {
// 执行提交逻辑
const result = await submitToAPI(formData);
// 返回的结果会成为新的 state
return { success: true, message: '提交成功!' };
},
{ success: false, message: '' } // 初始状态
);
return (
<form action={formAction}>
<input name="email" />
<button type="submit" disabled={isPending}>
{isPending ? '提交中...' : '提交'}
</button>
{/* 显示提交结果 */}
{state.message && <p>{state.message}</p>}
</form>
);
}
- 使用位置不同
useFormStatus:useFormStatus:必须在子组件中
js
// 正确:在子组件中使用
function Form() {
return (
<form action={submitAction}>
<SubmitButton /> {/* useFormStatus 在这里 */}
</form>
);
}
// 错误:在表单组件本身使用
function Form() {
const { pending } = useFormStatus(); // pending 永远是 false!
return <form action={submitAction}>...</form>;
}
useActionState:通常在表单组件本身
js
// 在表单组件中使用
function Form() {
const [state, formAction] = useActionState(action, initialState);
return <form action={formAction}>...</form>;
}
- 访问的数据类型不同
useFormStatus - 访问原始表单数据
js
function StatusReader() {
const { data } = useFormStatus();
// data 是 FormData 对象,包含用户输入的原始值
const password = data?.get('password'); // 原始字符串
const file = data?.get('avatar'); // File 对象
}
useActionState - 访问处理后的结果
js
const [state, formAction] = useActionState(async (prev, formData) => {
const user = await createUser(formData);
// state 可以是任何类型:对象、数组、字符串等
return { userId: user.id, message: '创建成功' };
}, null);
// 使用 state
console.log(state?.userId); // 业务数据
console.log(state?.message); // 提示信息
组合使用:两者并不互斥
它们可以同时使用,解决不同层面的问题:
js
// 主表单组件:使用 useActionState 管理结果
function FeedbackForm() {
const [state, formAction] = useActionState(submitFeedback, null);
return (
<form action={formAction}>
<textarea name="feedback" />
{/* 子组件:使用 useFormStatus 管理提交过程 */}
<SubmitButton />
{state?.success && <p>感谢反馈!</p>}
{state?.error && <p>提交失败,请重试</p>}
</form>
);
}
// 子组件:只关心提交过程
function SubmitButton() {
const { pending } = useFormStatus();
return (
<button type="submit" disabled={pending}>
{pending ? '发送中...' : '发送反馈'}
</button>
);
}
表单防重复提交
在上面的useActionState中已经有 isPending 可以自动管理 pending 状态。下面我们再学习另一个防止重复提交的hook。
useTransition 是 React 官方提供的标准 Hook,用于处理并发渲染场景下的非紧急状态更新。
useTransition 返回一个包含两个元素的数组,用于管理非紧急状态更新的过渡效果,特别适合处理表单提交等异步操作。
- isPending: boolean - 表示过渡是否正在进行中
- startTransition: function - 用于将状态更新标记为过渡的函数
js
import { useTransition, useEffect } from "react";
export const UseTransitionForm = () => {
// 防止重复提交:isPending 为 true 时不允许新提交
const [isPending, startTransition] = useTransition();
const handleSubmit = (formData: FormData) => {
console.log('1. 函数开始执行, isPending:', isPending); // false
// 这里的更新会被标记为"过渡"
// 可以包含同步或异步操作
startTransition(async () => {
console.log('2. 进入 startTransition 回调');
console.log('提交的表单数据:', Object.fromEntries(formData.entries()));
try {
// 模拟 API 调用
await new Promise(resolve => setTimeout(resolve, 1000));
console.log('提交完成');
} catch (error) {
console.error('提交失败', error);
}
});
// 注意:startTransition 调用后,isPending 仍然是 false,直到下一个渲染周期才会更新
console.log('4. startTransition 调用完成, isPending:', isPending); // 仍然是 false
};
// 在下一次渲染时,isPending 会变为 true
useEffect(() => {
console.log('组件重新渲染, isPending:', isPending);
});
return (
<div>
<h2>useTransition 表单</h2>
<form action={handleSubmit}>
<input name="name" />
<button type="submit" disabled={isPending}>
{isPending ? '提交中...' : '提交'}
</button>
</form>
</div>
)
}

总结:
- 提交后立即禁用按钮(下一个渲染周期)
- 请求期间用户无法重复提交
- 请求完成后自动恢复
乐观更新(Optimistic Updates)
乐观更新的核心思路:用户提交操作时,不等服务器响应,先假设操作会成功,立即在界面上看到结果。等服务器返回结果后,要么确认更新,要么把界面回滚。
三步走:
- 立即更新:用户操作 → 界面马上改变(假设成功)
- 后台请求:同时发送真实的 API 请求
- 确认或回滚:
- 成功 → 什么都不用做(已经是正确的了)
- 失败 → 把界面改回原样,提示用户
乐观更新模式 VS 传统模式


useOptimistic 是 React 19 专为乐观更新设计的 Hook,使用方式如下
js
const [optimisticState, addOptimistic] = useOptimistic(
state, // 基础状态(真实数据的来源)
updateFunction // 可选:定义如何计算乐观状态
);
- 参数
| 参数 | 类型 | 说明 | 必填 |
|---|---|---|---|
| state | T | 基础状态值,通常是真实数据(来自 useState、props、store 等) | 是 |
| updateFunction | (currentState: T, optimisticValue: P) => T | 接收当前状态和乐观值,返回新的乐观状态 | 否 |
- 返回值
| 返回值 | 类型 | 说明 |
|---|---|---|
| optimisticState | T | 当前显示的乐观状态,可能是真实状态或临时乐观状态 |
| addOptimistic | (value: P) => void | 调用此函数会立即触发乐观更新,React 会重新渲染组件 |
关键点:
- addOptimistic 调用后立即同步更新(在当前事件循环中)
- addOptimistic 不返回 Promise,纯粹是 UI 层面的更新
- 乐观更新是暂时性的,等待真实数据返回后会覆盖
js
import { useOptimistic } from "react";
const mockPostRequest = (data: { message: string }): Promise<{ success: boolean }> => {
return new Promise((resolve) => {
setTimeout(() => {
console.log('模拟发送请求,表单数据:', data);
resolve({ success: true });
}, 1000);
});
}
export const UseOptimisticForm = () => {
const [optimisticMessages, addOptimisticMessage] = useOptimistic<
{ text: string, sending: boolean }[],
string
>(
// 初始状态,可以是一个空数组或者预加载的消息列表
[],
// 定义如何添加一个新的乐观消息
(state, newMessage) => [...state, { text: newMessage, sending: true }]
);
const handleSubmit = async (formData: FormData) => {
const message = formData.get('message') as string;
if (!message.trim()) return;
// 1. 立即添加一个乐观消息
addOptimisticMessage(message); // 同步执行
// 显示乐观消息。
// React 会在当前事件循环结束时重新渲染
try {
// 2. 模拟发送请求。
await mockPostRequest({ message });
// 成功:保持消息,移除"正在发送"标记
} catch (error) {
// 失败:乐观更新失败,需要移除这条消息
console.error('发送失败:', error);
// 可以在这里删除乐观添加的消息
}
}
return (
<>
<h2>乐观更新表单</h2>
<form action={handleSubmit}>
<input type="text" name="message" />
<button type="submit">发送</button>
</form>
<ul>
{optimisticMessages.map((msg, index) => (
<li key={index} style={{ color: msg.sending ? 'gray' : 'black' }}>
{/* 显示消息内容 */}
{msg.text} {msg.sending && '(正在发送...)'}
</li>
))}
</ul>
</>
);
}
记住核心:useOptimistic 只负责 UI 的即时反馈,真正的数据同步需要你自己处理!
客户端 Action 和 服务端 Action
在 React 应用中,客户端 Action 和 服务端 Action 是两种不同的处理异步操作的方式。
什么是客户端 Action?
在客户端(浏览器)执行的异步函数,通常处理不需要服务端参与的 UI 逻辑。
- 语法:在文件顶部声明
'use client',标记该文件及其导入的模块为 客户端组件。 - 代码会在浏览器中执行,可以访问 window、document等浏览器 API,以及 React 状态、事件、hooks。
- 通常用于交互性强的 UI 部分。
什么是服务端 Action?
在服务端执行的异步函数,通过 'use server'标记,可以安全地处理敏感操作。
- 语法:在文件顶部声明
'use server',标记该函数为 服务端函数。 - 该函数仅在服务端执行,不会将实现逻辑发送到客户端。
- 用于安全地执行数据库操作、API 调用等敏感逻辑。
| 特性 | 客户端 Action | 服务端 Action |
|---|---|---|
| 执行环境 | 浏览器 | (客户端) |
| 指令标识 | 无需特殊指令或在文件顶部声明 'use client' |
'use server' |
| 典型场景 | 调用第三方 API、本地缓存、UI 状态更新 | 数据库操作、文件系统访问、敏感逻辑 |
| 代码体积 | 会打包到客户端 bundle | 不会发送到客户端,仅保留引用 |
| 与表单集成 | 通过 action 属性 | 通过 action 属性 |
| 需要 API 路由 | 不需要(直接调用) | 不需要(框架自动处理) |
客户端 Action
js
'use client';
async function createUserAction(formData) {
// 直接在客户端执行
const name = formData.get('name');
const res = await fetch('/api/users', {
method: 'POST',
body: JSON.stringify({ name }),
});
if (!res.ok) throw new Error('创建失败');
return { success: true };
}
function UserForm() {
return (
<form action={createUserAction}>
<input name="name" />
<button type="submit">提交</button>
</form>
);
}
服务端 Action
- 在 Server Component 中定义
js
// Server Component
import Button from './Button';
function EmptyNote() {
async function createNoteAction() {
'use server'; // 标记为服务端 Action
await db.notes.create(); // 直接操作数据库
}
return <Button onClick={createNoteAction} />;
}
js
// Client Component (Button)
'use client';
export default function Button({ onClick }) {
// onClick 是一个引用,不是函数体
console.log(onClick);
// { $$typeof: Symbol.for("react.server.reference"), $$id: 'createNoteAction' }
return <button onClick={() => onClick()}>创建笔记</button>;
}
- 从独立文件导入
js
// actions.ts
'use server';
export async function updateName(name: string) {
if (!name) {
return { error: '姓名不能为空' };
}
await db.users.updateName(name);
return { success: true };
}
js
// Client Component
'use client';
import { updateName } from './actions';
function UpdateName() {
return (
<form action={updateName}>
<input name="name" />
<button type="submit">更新</button>
</form>
);
}
与表单 Action 的集成
服务端 Action 可以与表单的 action 属性无缝集成:
js
'use client';
import { updateName } from './actions';
function UpdateName() {
return (
<form action={updateName}>
<input name="name" />
<button type="submit">更新</button>
</form>
);
}
当表单提交时:
- React 将表单数据序列化
- 发送 HTTP 请求到服务器
- 服务器执行 updateName 函数
- 结果返回客户端,表单自动重置
配合 useActionState 使用(推荐)
无论客户端还是服务端 Action,都可以配合 useActionState 来管理状态和加载状态:
js
'use client';
import { useActionState } from 'react';
import { updateName } from './actions'; // 服务端 Action
function UpdateName() {
const [state, submitAction, isPending] = useActionState(
updateName, // 可以是客户端或服务端 Action
{ error: null } // 初始状态
);
return (
<form action={submitAction}>
<input name="name" disabled={isPending} />
{state.error && <span>{state.error}</span>}
<button type="submit" disabled={isPending}>
{isPending ? '提交中...' : '更新'}
</button>
</form>
);
}
注意:useFormState 在 React 19 中已重命名为 useActionState,并从 react-dom 移至 react。
渐进增强
服务端 Action 配合 useActionState 的第三个参数 permalink,可以实现渐进增强------即使用户的 JavaScript 还未加载完成,表单依然可以正常提交:
js
'use client';
import { useActionState } from 'react';
import { updateName } from './actions';
function UpdateName() {
const [, submitAction] = useActionState(
updateName,
null,
'/name/update' // permalink:JS 未加载时 fallback 到此 URL
);
return (
<form action={submitAction}>
<input name="name" />
<button type="submit">更新</button>
</form>
);
}
总结:
- 客户端 Action:直接执行,适合调用外部 API
- 服务端 Action:通过 'use server' 标记,代码留在服务器,适合数据库等敏感操作
- 统一的使用方式:都可以传给 ,配合 useActionState 和 useFormStatus 管理状态
- 不需要手动写 API 路由:框架自动处理服务端 Action 的网络通信