跳到内容
App Router入门更新数据

更新数据

您可以使用 React 的服务器函数在 Next.js 中更新数据。本页将介绍如何创建调用服务器函数。

什么是服务器函数?

服务器函数是在服务器上运行的异步函数。它们可以通过网络请求从客户端调用,因此必须是异步的。

在`action`或 mutation 上下文中,它们也被称为服务器操作

按照惯例,服务器操作是与`startTransition`一起使用的异步函数。当函数符合以下情况时,这会自动发生:

  • 通过 `action` 属性传递给 `
    `。
  • 通过 `formAction` 属性传递给 `

在 Next.js 中,服务器操作与框架的缓存架构集成。当调用一个操作时,Next.js 可以在一次服务器往返中返回更新后的 UI 和新数据。

在后台,操作使用 `POST` 方法,并且只有这个 HTTP 方法才能调用它们。

创建服务器函数

可以通过使用`use server`指令来定义服务器函数。您可以将该指令放在异步函数的顶部,以将该函数标记为服务器函数,或者放在单独文件的顶部,以标记该文件的所有导出。

app/lib/actions.ts
export async function createPost(formData: FormData) {
  'use server'
  const title = formData.get('title')
  const content = formData.get('content')
 
  // Update data
  // Revalidate cache
}
 
export async function deletePost(formData: FormData) {
  'use server'
  const id = formData.get('id')
 
  // Update data
  // Revalidate cache
}

服务器组件

通过在函数体顶部添加 `"use server"` 指令,可以将服务器函数内联到服务器组件中。

app/page.tsx
export default function Page() {
  // Server Action
  async function createPost(formData: FormData) {
    'use server'
    // ...
  }
 
  return <></>
}

友情提示:服务器组件默认支持渐进式增强,这意味着即使 JavaScript 尚未加载或已禁用,调用服务器操作的表单仍将提交。

客户端组件

无法在客户端组件中定义服务器函数。但是,您可以通过从顶部带有 `“use server”` 指令的文件导入它们来在客户端组件中调用它们。

app/actions.ts
'use server'
 
export async function createPost() {}
app/ui/button.tsx
'use client'
 
import { createPost } from '@/app/actions'
 
export function Button() {
  return <button formAction={createPost}>Create</button>
}

友情提示:在客户端组件中,如果 JavaScript 尚未加载,调用服务器操作的表单将排队提交,并优先进行注水。注水后,浏览器不会在表单提交时刷新。

将操作作为 props 传递

您还可以将操作作为 prop 传递给客户端组件。

<ClientComponent updateItemAction={updateItem} />
app/client-component.tsx
'use client'
 
export default function ClientComponent({
  updateItemAction,
}: {
  updateItemAction: (formData: FormData) => void
}) {
  return <form action={updateItemAction}>{/* ... */}</form>
}

调用服务器函数

调用服务器函数主要有两种方式:

  1. 服务器组件和客户端组件中的表单
  2. 客户端组件中的事件处理程序useEffect

友情提示:服务器函数是为服务器端突变设计的。客户端目前一次调度并等待一个。这是一个实现细节,可能会改变。如果您需要并行数据获取,请在服务器组件中使用数据获取,或在单个服务器函数或路由处理程序中执行并行工作。

表单

React 扩展了 HTML `<form>`元素,允许使用 HTML `action` 属性调用服务器函数。

当在表单中调用时,函数会自动接收 `FormData` 对象。您可以使用原生 `FormData` 方法提取数据。

app/ui/form.tsx
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">Create</button>
    </form>
  )
}
app/actions.ts
'use server'
 
export async function createPost(formData: FormData) {
  const title = formData.get('title')
  const content = formData.get('content')
 
  // Update data
  // Revalidate cache
}

事件处理程序

您可以通过 `onClick` 等事件处理程序在客户端组件中调用服务器函数。

app/like-button.tsx
'use client'
 
import { incrementLike } from './actions'
import { useState } from 'react'
 
export default function LikeButton({ initialLikes }: { initialLikes: number }) {
  const [likes, setLikes] = useState(initialLikes)
 
  return (
    <>
      <p>Total Likes: {likes}</p>
      <button
        onClick={async () => {
          const updatedLikes = await incrementLike()
          setLikes(updatedLikes)
        }}
      >
        Like
      </button>
    </>
  )
}

示例

显示待处理状态

在执行服务器函数时,您可以使用 React 的`useActionState`钩子显示加载指示器。此钩子返回一个 `pending` 布尔值。

app/ui/button.tsx
'use client'
 
import { useActionState, startTransition } from 'react'
import { createPost } from '@/app/actions'
import { LoadingSpinner } from '@/app/ui/loading-spinner'
 
export function Button() {
  const [state, action, pending] = useActionState(createPost, false)
 
  return (
    <button onClick={() => startTransition(action)}>
      {pending ? <LoadingSpinner /> : 'Create Post'}
    </button>
  )
}

重新验证

执行更新后,您可以重新验证 Next.js 缓存,并通过在服务器函数中调用`revalidatePath``revalidateTag`来显示更新后的数据。

app/lib/actions.ts
import { revalidatePath } from 'next/cache'
 
export async function createPost(formData: FormData) {
  'use server'
  // Update data
  // ...
 
  revalidatePath('/posts')
}

重定向

在执行更新后,您可能希望将用户重定向到不同的页面。您可以通过在服务器函数中调用`redirect`来完成此操作。

app/lib/actions.ts
'use server'
 
import { revalidatePath } from 'next/cache'
import { redirect } from 'next/navigation'
 
export async function createPost(formData: FormData) {
  // Update data
  // ...
 
  revalidatePath('/posts')
  redirect('/posts')
}

调用 `redirect` 会抛出框架处理的控制流异常。其后的任何代码都不会执行。如果您需要新数据,请事先调用`revalidatePath``revalidateTag`

Cookies

您可以使用`cookies` API 在服务器操作中`get`、`set`和`delete` cookie。

当您在服务器操作中设置或删除 cookie 时,Next.js 会在服务器上重新渲染当前页面及其布局,以便 UI 反映新的 cookie 值

友情提示:服务器更新适用于当前的 React 树,根据需要重新渲染、挂载或卸载组件。客户端状态会为重新渲染的组件保留,如果其依赖项发生变化,效果会重新运行。

app/actions.ts
'use server'
 
import { cookies } from 'next/headers'
 
export async function exampleAction() {
  const cookieStore = await cookies()
 
  // Get cookie
  cookieStore.get('name')?.value
 
  // Set cookie
  cookieStore.set('name', 'Delba')
 
  // Delete cookie
  cookieStore.delete('name')
}

useEffect

您可以使用 React `useEffect`钩子在组件挂载或依赖项更改时调用服务器操作。这对于依赖全局事件或需要自动触发的突变非常有用。例如,用于应用程序快捷方式的 `onKeyDown`、用于无限滚动的交叉观察器钩子,或者在组件挂载时更新视图计数。

app/view-count.tsx
'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)
    })
  }, [])
 
  // You can use `isPending` to give users feedback
  return <p>Total Views: {views}</p>
}