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

相关推荐
zzzzzz3103 小时前
react-bits:酷炫动效之外,React 组件库还能提供什么
javascript·react.js·动效
web打印社区10 小时前
网页静默打印热敏小票:从 HTML 到出纸的完整指南
前端·javascript·vue.js·electron·pdf·html
剪刀石头布啊11 小时前
设计一个多计时任务的任务管理功能
前端
ModyQyW12 小时前
vite-plugin-uni-manifest 更新了什么
前端·typescript·uni-app
Asize13 小时前
AI 协作开发新范式:我用 SDD 做了个排版 npm 包
前端·人工智能
kyriewen14 小时前
我把今年流传的前端 AI 面试题整理了一遍——4 类场景题+回答框架(附速查表)
前端·面试·程序员
计算机魔术师15 小时前
国产多模态模型正面硬刚Opus旗舰:差距从30%缩到3%
前端
风骏时光牛马15 小时前
程序员进阶:深度思考,解锁职场成长的底层逻辑
前端
IT_陈寒15 小时前
用了Proxy才发现以前的JavaScript白写了
前端·人工智能·后端
爱丶不疚16 小时前
Eval: Agent 说的 Eval 是什么?从单测、TDD 到 Sentry 聊起
前端·ai编程·vibecoding