文章目录

React Server Actions在表单提交中的使用模式

发布于 2026-05-05 14:17:19 · 浏览 11 次 · 评论 0 条

React Server Actions 在表单提交中的使用模式

React Server Actions 允许你在服务端直接运行函数,而无需手动创建 API 路由。这种方式极大地简化了表单提交流程。以下是几种核心使用模式的实操指南。


模式一:基础表单提交

这是最直接的模式,适用于简单的数据提交场景,无需复杂的客户端状态管理。

1. 定义 Server Action

app/actions.ts 文件中创建一个异步函数,并在文件顶部添加 'use server' 指令。

// app/actions.ts
'use server'

import { revalidatePath } from 'next/cache'

export async function createTodo(formData: FormData) {
  const title = formData.get('title') as string

  // **执行**数据库操作
  await db.todo.create({
    data: {
      title,
    },
  })

  // **刷新**缓存以更新 UI
  revalidatePath('/')
}

2. 绑定 Action 到表单

在组件中导入刚才创建的函数,并将其传递<form> 元素的 action 属性。

// app/page.tsx
import { createTodo } from './actions'

export default function Page() {
  return (
    <form action={createTodo}>
      <input 
        type="text" 
        name="title" 
        required 
        placeholder="输入待办事项..."
      />
      <button type="submit">**提交**</button>
    </form>
  )
}

在此模式下,浏览器会自动将表单数据作为 FormData 对象发送给服务端函数,无需手动编写 fetch 请求。


模式二:提交后重定向

在用户提交表单成功后,通常需要将其跳转到其他页面。

1. 引入 redirect 函数

next/navigation 导入 redirect 函数。

2. 在逻辑末尾调用重定向

在 Server Action 的数据库操作完成后,调用 redirect指定目标路径。

// app/actions.ts
'use server'

import { redirect } from 'next/navigation'
import { db } from '@/lib/db'

export async function updateUser(formData: FormData) {
  const id = formData.get('id') as string
  const name = formData.get('name') as string

  await db.user.update({
    where: { id },
    data: { name },
  })

  // **强制**重定向到用户详情页
  redirect(`/users/${id}`)
}

注意redirect抛出一个错误以中断执行,因此请确保将其放置在所有逻辑的最后。


模式三:错误处理与表单状态

当需要向用户反馈服务器返回的错误信息(如验证失败、重复提交等)时,需使用 useFormState Hook。

1. 修改 Action 返回状态

调整 Server Action,使其不再直接抛出错误,而是返回一个包含错误信息的对象。

// app/actions.ts
'use server'

export async function submitForm(
  prevState: { message: string },
  formData: FormData
) {
  const email = formData.get('email') as string

  if (!email.includes('@')) {
    return { message: '请输入有效的邮箱地址' }
  }

  // 模拟 API 调用
  await new Promise(resolve => setTimeout(resolve, 1000))

  return { message: '提交成功!' }
}

2. 在客户端绑定 State

在客户端组件中(必须添加 'use client' 指令),使用 useFormState 包裹 Action。

// app/ContactForm.tsx
'use client'

import { useFormState } from 'react-dom'
import { submitForm } from './actions'

export default function ContactForm() {
  const [state, formAction] = useFormState(submitForm, { message: '' })

  return (
    <form action={formAction}>
      <input type="email" name="email" required />
      <button type="submit">发送</button>

      {/* **渲染**状态消息 */}
      <p style={{ color: state.message.includes('成功') ? 'green' : 'red' }}>
        {state.message}
      </p>
    </form>
  )
}

3. 模式对比

下表总结了不同场景下的处理方式差异:

场景需求 推荐模式 关键 API/Hook 返回值处理
简单数据写入 模式一 action={fn} 无需处理,自动刷新
提交后跳转 模式二 redirect(url) 抛出 NextResponse
显示错误/成功消息 模式三 useFormState 返回 State 对象

模式四:提交中的待定状态

为了提升用户体验,需要在提交过程中禁用按钮并显示加载动画。这需要配合 useFormStatus Hook 使用。

1. 提取按钮为独立组件

将提交按钮拆分到一个独立的子组件中,并标记'use client'

// app/SubmitButton.tsx
'use client'

import { useFormStatus } from 'react-dom'

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

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

注意useFormStatus 必须在 <form>内部使用,它才会自动关联父级表单的状态。

2. 在主表单中引用

在主表单组件中替换原有的 <button> 标签为新组件。

// app/page.tsx
'use client'

import { useFormState } from 'react-dom'
import { submitForm } from './actions'
import { SubmitButton } from './SubmitButton'

export default function FormWithPending() {
  const [state, formAction] = useFormState(submitForm, null)

  return (
    <form action={formAction}>
      <input type="text" name="username" />
      <SubmitButton />
      {state?.message && <p>{state.message}</p>}
    </form>
  )
}

模式五:处理复杂数据结构

默认的 FormData 只能处理字符串或文件。如果表单包含复杂对象嵌套数据,需在客户端进行预处理,或将数据序列化为 JSON 字符串传递。

1. 客户端序列化

action 调用前(通常通过 action 属性直接处理时很难做到,更推荐使用调用方式),或者使用隐藏字段存储 JSON。

如果必须使用表单提交模式,可以在表单中添加隐藏字段:

// 假设 complexData 是一个对象
<input 
  type="hidden" 
  name="complexData" 
  value={JSON.stringify(complexData)} 
/>

2. 服务端解析

在 Server Action 中获取该字段并解析 JSON。

// app/actions.ts
'use server'

export async function handleComplexData(formData: FormData) {
  const rawData = formData.get('complexData') as string

  // **解析** JSON 字符串
  try {
    const data = JSON.parse(rawData)
    await db.process(data)
  } catch (e) {
    console.error('数据解析失败', e)
  }
}

这种方式适用于需要配合浏览器默认表单行为,但又需传递结构化数据的场景。

评论 (0)

暂无评论,快来抢沙发吧!

扫一扫,手机查看

扫描上方二维码,在手机上查看本文