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

相关推荐
子兮曰2 天前
jev-ultrafast 深度解析:7 秒订机票的浏览器 Agent 是如何炼成的
前端·后端·agent
子兮曰2 天前
Jev 爆发一周:7 秒 Agent 背后的 System One 生态与三场争议
前端·后端·ai编程
前端小万2 天前
写公众号赚了 3000 块后,我做了一款叫 "一键成稿" 的软件
前端·微信小程序
爱勇宝2 天前
ZCode 开源 24 小时:一份没有历史的账本,回答不了"有没有偷代码"
前端·后端·chatglm (智谱)
三十而立洋2 天前
Cookie 详解:从产生到安全,一次讲透
前端·javascript
卡布鲁2 天前
把一个 Vite + Vue3 应用塞进 qiankun (React + Umi3) 主站:十个坑的复盘
前端·javascript·react.js
李少兄2 天前
JavaScript 隐式全局变量解析
javascript
汉堡大王95272 天前
Jev:不是聊天机器人, 而是一个智能 if 语句
前端·人工智能·后端
梦想很大很大2 天前
从运行事实到回归证据:Workrun 的 Telemetry 与 Evaluation 实践
前端·人工智能·后端
计算机魔术师2 天前
Meta Muse agent 接入 Shopify 的 Shop Pay 实现代理式购物
前端