React

关注公众号 jb51net

关闭
首页 > 网络编程 > JavaScript > javascript类库 > React > React useActionState 用法

React useActionState 的用法详解

作者:sunly_

useActionState是React19新出的Hook,能帮你统一管理异步请求、表单校验和提交状态,本文用简单示例讲清它的用法、和useState/useReducer的区别,还有5个常见错误避坑指南,感兴趣的可以了解一下

一、useActionState 是什么

useActionState 是 React 19 新增的 Hook,主要用于处理:

可以简单理解为:

useActionState = useState + 异步 Action + 提交状态管理

React 官方定义:

const [state, action, isPending] = useActionState(
  actionFunction,
  initialState
)

具体 API 可以参考:React 官方 useActionState 文档

二、基本语法

import { useActionState } from 'react'

const [state, formAction, isPending] = useActionState(
  actionFunction,
  initialState
)

返回三个值:

1. state

当前状态,初始值就是传入的 initialState

2. formAction

触发 Action 的函数,可以传给:

<form action={formAction}>

3. isPending

表示 Action 是否正在执行:

isPending === true

说明当前正在提交或请求中。

三、最简单的表单示例

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

四、代码执行过程

当点击提交按钮时:

提交表单
   ↓
调用 submitAction
   ↓
获取 FormData
   ↓
执行校验或接口请求
   ↓
返回新的 state
   ↓
组件重新渲染

Action 函数接收两个参数:

async function submitAction(previousState, formData) {
  // ...
}

previousState

上一次 Action 返回的状态。

第一次执行时,它就是初始状态:

{
  success: false,
  message: ''
}

formData

表单数据:

const username = formData.get('username')

注意,表单控件必须设置 name:

<input name="username" />

没有 name 的表单元素不会出现在 FormData 中。

五、处理表单校验

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 很适合统一管理表单状态:

{
  success: false,
  message: '用户名或密码错误'
}

六、useActionState 处理异步请求

实际开发中,Action 中通常会发送请求:

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: '网络错误,请稍后重试'
    }
  }
}

组件中:

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。

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

手动调用时需要使用:

startTransition(() => {
  dispatchAction()
})

React 官方说明,手动调用 dispatchAction 时需要放在 Action 或 startTransition 中;如果传给 <form action>,React 会自动处理 Transition。React 官方 useActionState 文档

八、传递 Action 参数

可以给 Action 传递对象:

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

这种写法适合处理多个操作类型:

{
  type: 'add'
}
{
  type: 'remove'
}

九、useActionState 和 useState 的区别

对比项useStateuseActionState
主要用途管理普通组件状态管理 Action 或表单提交状态
是否支持异步 Action需要自己处理内置支持
是否有 pending 状态需要自己定义自动提供
是否接收 previousState不会
是否适合表单可以,但代码较多非常适合
是否需要手动处理结果通常需要Action 返回值就是新状态

使用 useState:

const [loading, setLoading] = useState(false)
const [message, setMessage] = useState('')

async function handleSubmit() {
  setLoading(true)

  try {
    await fetch('/api/user')
    setMessage('提交成功')
  } finally {
    setLoading(false)
  }
}

使用 useActionState:

const [state, formAction, isPending] =
  useActionState(submitAction, initialState)

useActionState 可以减少表单提交中的重复代码。

十、useActionState 和 useReducer 的区别

两者写法比较相似:

const [state, dispatch] = useReducer(reducer, initialState)
const [state, dispatchAction, isPending] =
  useActionState(action, initialState)

主要区别:

对比项useReduceruseActionState
reducer 是否应该纯函数可以执行副作用
是否适合异步请求需要额外处理直接支持
是否有 pending 状态没有
主要用途管理复杂 UI 状态管理异步 Action 状态
是否按顺序处理 Action一般不强调会按顺序排队处理

简单记忆:

useReducer:管理复杂状态
useActionState:管理异步操作结果

十一、useFormStatus 配合提交按钮

如果提交按钮是一个独立组件,可以使用 useFormStatus 获取表单的提交状态。

import { useFormStatus } from 'react-dom'

function SubmitButton() {
  const { pending } = useFormStatus()

  return (
    <button
      type="submit"
      disabled={pending}
    >
      {pending ? '提交中...' : '提交'}
    </button>
  )
}

使用:

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

错误:

<input />

正确:

<input name="username" />

否则无法通过 formData.get('username') 获取数据。

2. Action 参数顺序写错

错误:

async function submitAction(formData, previousState) {
  // ...
}

正确:

async function submitAction(previousState, formData) {
  // ...
}

3. 手动调用时没有使用 startTransition

错误:

function handleClick() {
  dispatchAction()
}

正确:

function handleClick() {
  startTransition(() => {
    dispatchAction()
  })
}

但如果这样使用:

<form action={formAction}>

通常不需要手动调用 startTransition

4. Action 没有返回新状态

错误:

async function submitAction(previousState, formData) {
  await fetch('/api/user')
}

正确:

async function submitAction(previousState, formData) {
  await fetch('/api/user')

  return {
    success: true,
    message: '提交成功'
  }
}

5. 用 useActionState 管理普通输入框

如果只是管理输入框内容,使用 useState 更简单:

const [value, setValue] = useState('')

useActionState 更适合提交、请求、校验等 Action 场景。

十三、总结

useActionState 的基本写法:

const [state, formAction, isPending] =
  useActionState(actionFunction, initialState)

Action 函数:

async function actionFunction(previousState, formData) {
  // 表单校验
  // 发送请求
  // 返回最新状态

  return newState
}

表单使用:

<form action={formAction}>
  <input name="username" />
  <button disabled={isPending}>
    提交
  </button>
</form>

一句话记忆:

useActionState 主要用于把异步请求、表单提交结果和 loading 状态统一管理起来。

到此这篇关于React useActionState 的用法详解的文章就介绍到这了,更多相关React useActionState 用法内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文