React useActionState 的用法详解

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 状态统一管理起来。

相关推荐
今日无bug1 小时前
RESTful + Interface:从 todos 项目理解后端接口设计
前端·restful
用户69371750013841 小时前
#DeepSeek+Pi‑Agent 王炸组合跑赢 Claude‑Code!
前端·人工智能·后端
前端一课1 小时前
我还在上班,怎么做一个长期有价值的号
前端
前端一课2 小时前
用 TRAE Work 把项目踩坑经验沉淀成「团队可复用工程规范」,新人再也不重复掉坑
前端·后端
cyadyx2 小时前
【Three.js】Three.js 中加载 3D 模型
前端·javascript·3d
汉堡大王95272 小时前
前端工程师 TypeScript 上手指南:一条清晰的从入门到精通路线
前端·javascript·react.js
wear工程师2 小时前
防抖为什么会失效?React 面试里的闭包和定时器
javascript·react.js
浅水壁虎2 小时前
vue基础(第四章 Pinia)
前端·javascript·vue.js
用户2181697049302 小时前
Flutter (十四) SingleChildScrollView ListView
前端