首页 / Next.js 16 入门教程 / 数据变更

Next.js 16 入门教程

数据变更

本教程共 42 篇 · 第 10 篇 · 更新于 2026-07-30 · 约 8 分钟阅读

Next.jsNext.js 16 入门教程Server Actions数据变更表单revalidate

10. 数据变更

本节目标:学会用 Server Actions 处理数据变更,掌握表单提交、缓存清除和跳转的完整流程。

什么是 Server Actions

Server Actions 是在服务器端运行的异步函数,可以从客户端调用。它们用来处理数据变更——创建、更新、删除操作。

// app/actions.ts
'use server'

export async function createPost(formData: FormData) {
  const title = formData.get('title')
  const content = formData.get('content')

  await db.post.create({
    data: { title, content }
  })
}
Warning

Server Actions 可以通过 POST 请求直接调用。务必在函数内部验证用户身份和权限。

创建 Server Actions

独立文件

在文件顶部加 'use server',所有导出函数都是 Server Actions:

// app/lib/actions.ts
'use server'

import { auth } from '@/lib/auth'

export async function createPost(formData: FormData) {
  const session = await auth()
  if (!session?.user) {
    throw new Error('未登录')
  }

  const title = formData.get('title')
  const content = formData.get('content')

  await db.post.create({
    data: { title, content, authorId: session.user.id }
  })
}

内联在 Server Component

export default function Page() {
  async function createPost(formData: FormData) {
    'use server'
    // ...
  }

  return <form action={createPost}>...</form>
}

调用 Server Actions

表单调用

Server Component 中直接用 action prop:

import { createPost } from '@/app/actions'

export function Form() {
  return (
    <form action={createPost}>
      <input type="text" name="title" />
      <input type="text" name="content" />
      <button type="submit">创建</button>
    </form>
  )
}

Client Component 中通过 import 调用:

'use client'

import { createPost } from '@/app/actions'

export function Form() {
  return (
    <form action={createPost}>
      <input type="text" name="title" />
      <button type="submit">创建</button>
    </form>
  )
}

事件处理中调用

'use client'

import { incrementLike } from './actions'
import { useState } from 'react'

export default function LikeButton({ initialLikes }: { initialLikes: number }) {
  const [likes, setLikes] = useState(initialLikes)

  return (
    <>
      <p>总赞数:{likes}</p>
      <button
        onClick={async () => {
          const updatedLikes = await incrementLike()
          setLikes(updatedLikes)
        }}
      >
        点赞
      </button>
    </>
  )
}

用 useActionState 处理状态

'use client'

import { useActionState } from 'react'
import { createPost } from '@/app/actions'

export function Form() {
  const [state, formAction, pending] = useActionState(createPost, null)

  return (
    <form action={formAction}>
      <input type="text" name="title" />
      <button disabled={pending}>
        {pending ? '提交中...' : '创建'}
      </button>
    </form>
  )
}

useActionState 返回三个值:

  • state:Server Action 的返回值
  • formAction:传给 <form action={...}> 的函数
  • pending:是否正在执行

表单验证

Server Action 里可以验证表单数据:

'use server'

export async function createPost(prevState: any, formData: FormData) {
  const title = formData.get('title') as string
  const content = formData.get('content') as string

  if (!title || title.length < 3) {
    return { message: '标题至少 3 个字符' }
  }

  if (!content) {
    return { message: '内容不能为空' }
  }

  await db.post.create({ data: { title, content } })
  return { message: '创建成功' }
}

重新验证缓存

数据变更后,通常要清除缓存,让 UI 显示最新数据。

revalidatePath

按路径清除缓存:

'use server'

import { revalidatePath } from 'next/cache'

export async function createPost(formData: FormData) {
  // ... 创建文章
  revalidatePath('/posts')
}

revalidateTag

按标签清除缓存:

'use server'

import { revalidateTag } from 'next/cache'

export async function createPost(formData: FormData) {
  // ... 创建文章
  revalidateTag('posts', 'max')
}
Note

Next.js 16 中,revalidateTag 需要传入第二个参数指定缓存生命周期配置(如 'max')。单参数写法已废弃。

updateTag

立即过期缓存(用户立刻看到变更):

'use server'

import { updateTag } from 'next/cache'

export async function createPost(formData: FormData) {
  // ... 创建文章
  updateTag('posts')
}
方法使用场景行为
revalidatePath不知道标签时清除路径下所有缓存
revalidateTag允许短暂延迟先返回旧数据,后台刷新
updateTag用户要立刻看到变更立即过期,下次请求重新获取

跳转

数据变更后跳转到其他页面:

'use server'

import { revalidatePath } from 'next/cache'
import { redirect } from 'next/navigation'

export async function createPost(formData: FormData) {
  const post = await db.post.create({
    data: { /* ... */ }
  })

  revalidatePath('/posts')
  redirect(`/posts/${post.id}`)
}
Note

redirect 会抛出异常,之后的代码不会执行。如果需要先清除缓存,在 redirect 之前调用。

Server Actions 可以读写 Cookie:

'use server'

import { cookies } from 'next/headers'

export async function setTheme(theme: string) {
  const cookieStore = await cookies()
  cookieStore.set('theme', theme)
}

设置或删除 Cookie 后,Next.js 会重新渲染当前页面和布局,UI 立即反映新值。

useEffect 中调用

有些场景需要在组件加载时触发变更:

'use client'

import { incrementViews } from './actions'
import { useState, useEffect, useTransition } from 'react'

export default function ViewCount({ initialViews }: { initialViews: number }) {
  const [views, setViews] = useState(initialViews)
  const [isPending, startTransition] = useTransition()

  useEffect(() => {
    startTransition(async () => {
      const updatedViews = await incrementViews()
      setViews(updatedViews)
    })
  }, [])

  return <p>总浏览:{views}</p>
}

安全注意事项

  1. 验证身份:每个 Server Action 都要检查用户是否登录
  2. 验证权限:检查用户是否有权操作目标资源
  3. 验证输入:不要信任客户端传来的数据
  4. 限制频率:防止滥用(可用第三方库)
'use server'

export async function deletePost(formData: FormData) {
  const session = await auth()
  if (!session?.user) {
    throw new Error('未登录')
  }

  const id = formData.get('id') as string
  const post = await db.post.findUnique({ where: { id } })

  if (post?.authorId !== session.user.id) {
    throw new Error('无权删除')
  }

  await db.post.delete({ where: { id } })
  revalidatePath('/posts')
}

小结

这一章我们学了数据变更的完整方案:

  1. 'use server' 定义 Server Action
  2. 表单用 action={fn} 调用
  3. 事件处理直接 await fn()
  4. useActionState 管理 pending 状态
  5. revalidatePath/revalidateTag/updateTag 清除缓存
  6. redirect 跳转页面
  7. 务必验证身份和权限

下一章,我们来深入学习缓存机制。