React useActionState 的用法详解
一、useActionState 是什么
useActionState 是 React 19 新增的 Hook,主要用于处理:
- 表单提交
- 异步请求
- 提交中的 loading 状态
- 成功或失败信息
- 根据上一次结果更新下一次状态
可以简单理解为:
useActionState=useState+ 异步 Action + 提交状态管理
React 官方定义:
jsx
const [state, action, isPending] = useActionState(
actionFunction,
initialState
)
具体 API 可以参考:React 官方 useActionState 文档
二、基本语法
jsx
import { useActionState } from 'react'
const [state, formAction, isPending] = useActionState(
actionFunction,
initialState
)
返回三个值:
1. state
当前状态,初始值就是传入的 initialState。
2. formAction
触发 Action 的函数,可以传给:
jsx
<form action={formAction}>
3. isPending
表示 Action 是否正在执行:
jsx
isPending === true
说明当前正在提交或请求中。
三、最简单的表单示例
jsx
import { useActionState } from 'react'
async function submitAction(previousState, formData) {
const username = formData.get('username')
if (!username) {
return {
success: false,
message: '请输入用户名'
}
}
await new Promise(resolve => {
setTimeout(resolve, 1000)
})
return {
success: true,
message: `提交成功,用户名是:${username}`
}
}
function App() {
const [state, formAction, isPending] = useActionState(
submitAction,
{
success: false,
message: ''
}
)
return (
<form action={formAction}>
<input
name="username"
placeholder="请输入用户名"
/>
<button disabled={isPending}>
{isPending ? '提交中...' : '提交'}
</button>
<p>{state.message}</p>
</form>
)
}
export default App
四、代码执行过程
当点击提交按钮时:
text
提交表单
↓
调用 submitAction
↓
获取 FormData
↓
执行校验或接口请求
↓
返回新的 state
↓
组件重新渲染
Action 函数接收两个参数:
jsx
async function submitAction(previousState, formData) {
// ...
}
previousState
上一次 Action 返回的状态。
第一次执行时,它就是初始状态:
jsx
{
success: false,
message: ''
}
formData
表单数据:
jsx
const username = formData.get('username')
注意,表单控件必须设置 name:
jsx
<input name="username" />
没有 name 的表单元素不会出现在 FormData 中。
五、处理表单校验
jsx
import { useActionState } from 'react'
async function loginAction(previousState, formData) {
const username = formData.get('username')
const password = formData.get('password')
if (!username || !password) {
return {
success: false,
message: '用户名和密码不能为空'
}
}
if (username !== 'admin' || password !== '123456') {
return {
success: false,
message: '用户名或密码错误'
}
}
return {
success: true,
message: '登录成功'
}
}
function LoginForm() {
const [state, formAction, isPending] = useActionState(
loginAction,
{
success: false,
message: ''
}
)
return (
<form action={formAction}>
<input
name="username"
placeholder="用户名"
/>
<input
name="password"
type="password"
placeholder="密码"
/>
<button disabled={isPending}>
{isPending ? '登录中...' : '登录'}
</button>
<p
style={{
color: state.success ? 'green' : 'red'
}}
>
{state.message}
</p>
</form>
)
}
export default LoginForm
useActionState 很适合统一管理表单状态:
jsx
{
success: false,
message: '用户名或密码错误'
}
六、useActionState 处理异步请求
实际开发中,Action 中通常会发送请求:
jsx
async function submitAction(previousState, formData) {
const username = formData.get('username')
try {
const response = await fetch('/api/user', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
username
})
})
const result = await response.json()
if (!response.ok) {
return {
success: false,
message: result.message || '提交失败'
}
}
return {
success: true,
message: '提交成功'
}
} catch (error) {
return {
success: false,
message: '网络错误,请稍后重试'
}
}
}
组件中:
jsx
function App() {
const [state, formAction, isPending] = useActionState(
submitAction,
{
success: false,
message: ''
}
)
return (
<form action={formAction}>
<input name="username" />
<button disabled={isPending}>
{isPending ? '保存中...' : '保存'}
</button>
<p>{state.message}</p>
</form>
)
}
七、手动调用 Action
除了用于表单,也可以手动调用返回的 Action。
jsx
import {
startTransition,
useActionState
} from 'react'
async function updateCount(previousCount, amount) {
await new Promise(resolve => {
setTimeout(resolve, 1000)
})
return previousCount + amount
}
function Counter() {
const [count, dispatchAction, isPending] =
useActionState(updateCount, 0)
function handleAdd() {
startTransition(() => {
dispatchAction(1)
})
}
return (
<div>
<p>数量:{count}</p>
<button
onClick={handleAdd}
disabled={isPending}
>
{isPending ? '更新中...' : '加一'}
</button>
</div>
)
}
export default Counter
手动调用时需要使用:
jsx
startTransition(() => {
dispatchAction()
})
React 官方说明,手动调用 dispatchAction 时需要放在 Action 或 startTransition 中;如果传给 <form action>,React 会自动处理 Transition。React 官方 useActionState 文档
八、传递 Action 参数
可以给 Action 传递对象:
jsx
import {
startTransition,
useActionState
} from 'react'
async function cartAction(previousState, action) {
if (action.type === 'add') {
return previousState + 1
}
if (action.type === 'remove') {
return Math.max(0, previousState - 1)
}
return previousState
}
function Cart() {
const [count, dispatchAction, isPending] =
useActionState(cartAction, 0)
function handleAdd() {
startTransition(() => {
dispatchAction({
type: 'add'
})
})
}
function handleRemove() {
startTransition(() => {
dispatchAction({
type: 'remove'
})
})
}
return (
<div>
<p>购物车数量:{count}</p>
<button
onClick={handleAdd}
disabled={isPending}
>
加一
</button>
<button
onClick={handleRemove}
disabled={isPending}
>
减一
</button>
</div>
)
}
export default Cart
这种写法适合处理多个操作类型:
jsx
{
type: 'add'
}
jsx
{
type: 'remove'
}
九、useActionState 和 useState 的区别
| 对比项 | useState | useActionState |
|---|---|---|
| 主要用途 | 管理普通组件状态 | 管理 Action 或表单提交状态 |
| 是否支持异步 Action | 需要自己处理 | 内置支持 |
| 是否有 pending 状态 | 需要自己定义 | 自动提供 |
| 是否接收 previousState | 不会 | 会 |
| 是否适合表单 | 可以,但代码较多 | 非常适合 |
| 是否需要手动处理结果 | 通常需要 | Action 返回值就是新状态 |
使用 useState:
jsx
const [loading, setLoading] = useState(false)
const [message, setMessage] = useState('')
async function handleSubmit() {
setLoading(true)
try {
await fetch('/api/user')
setMessage('提交成功')
} finally {
setLoading(false)
}
}
使用 useActionState:
jsx
const [state, formAction, isPending] =
useActionState(submitAction, initialState)
useActionState 可以减少表单提交中的重复代码。
十、useActionState 和 useReducer 的区别
两者写法比较相似:
jsx
const [state, dispatch] = useReducer(reducer, initialState)
jsx
const [state, dispatchAction, isPending] =
useActionState(action, initialState)
主要区别:
| 对比项 | useReducer | useActionState |
|---|---|---|
| reducer 是否应该纯函数 | 是 | 可以执行副作用 |
| 是否适合异步请求 | 需要额外处理 | 直接支持 |
| 是否有 pending 状态 | 没有 | 有 |
| 主要用途 | 管理复杂 UI 状态 | 管理异步 Action 状态 |
| 是否按顺序处理 Action | 一般不强调 | 会按顺序排队处理 |
简单记忆:
text
useReducer:管理复杂状态
useActionState:管理异步操作结果
十一、useFormStatus 配合提交按钮
如果提交按钮是一个独立组件,可以使用 useFormStatus 获取表单的提交状态。
jsx
import { useFormStatus } from 'react-dom'
function SubmitButton() {
const { pending } = useFormStatus()
return (
<button
type="submit"
disabled={pending}
>
{pending ? '提交中...' : '提交'}
</button>
)
}
使用:
jsx
function App() {
const [state, formAction] = useActionState(
submitAction,
{
success: false,
message: ''
}
)
return (
<form action={formAction}>
<input name="username" />
<SubmitButton />
<p>{state.message}</p>
</form>
)
}
注意:
useFormStatus必须写在<form>的子组件中,不能在同一个组件中直接读取自己创建的<form>状态。
官方文档:useFormStatus
十二、常见错误
1. 忘记给表单元素添加 name
错误:
jsx
<input />
正确:
jsx
<input name="username" />
否则无法通过 formData.get('username') 获取数据。
2. Action 参数顺序写错
错误:
jsx
async function submitAction(formData, previousState) {
// ...
}
正确:
jsx
async function submitAction(previousState, formData) {
// ...
}
3. 手动调用时没有使用 startTransition
错误:
jsx
function handleClick() {
dispatchAction()
}
正确:
jsx
function handleClick() {
startTransition(() => {
dispatchAction()
})
}
但如果这样使用:
jsx
<form action={formAction}>
通常不需要手动调用 startTransition。
4. Action 没有返回新状态
错误:
jsx
async function submitAction(previousState, formData) {
await fetch('/api/user')
}
正确:
jsx
async function submitAction(previousState, formData) {
await fetch('/api/user')
return {
success: true,
message: '提交成功'
}
}
5. 用 useActionState 管理普通输入框
如果只是管理输入框内容,使用 useState 更简单:
jsx
const [value, setValue] = useState('')
useActionState 更适合提交、请求、校验等 Action 场景。
十三、总结
useActionState 的基本写法:
jsx
const [state, formAction, isPending] =
useActionState(actionFunction, initialState)
Action 函数:
jsx
async function actionFunction(previousState, formData) {
// 表单校验
// 发送请求
// 返回最新状态
return newState
}
表单使用:
jsx
<form action={formAction}>
<input name="username" />
<button disabled={isPending}>
提交
</button>
</form>
一句话记忆:
useActionState主要用于把异步请求、表单提交结果和 loading 状态统一管理起来。