Skip to content

更新数据

¥Updating Data

你可以使用 React 的 服务器函数 更新 Next.js 中的数据。本页将介绍如何使用 createinvoke 服务器功能。

¥You can update data in Next.js using React's Server Functions. This page will go through how you can create and invoke Server Functions.

什么是服务器函数?

¥What are Server Functions?

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

¥A Server Function is an asynchronous function that runs on the server. They can be called from client through a network request, which is why they must be asynchronous.

action 或突变上下文中,它们也称为服务器操作。

¥In an action or mutation context, they are also called Server Actions.

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

¥By convention, a Server Action is an async function used with startTransition. This happens automatically when the function is:

  • 使用 action 属性传递给 <form>

    ¥Passed to a <form> using the action prop.

  • 使用 formAction 属性传递给 <button>

    ¥Passed to a <button> using the formAction prop.

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

¥In Next.js, Server Actions integrate with the framework's caching architecture. When an action is invoked, Next.js can return both the updated UI and new data in a single server roundtrip.

在幕后,操作使用 POST 方法,并且只有此 HTTP 方法可以调用它们。

¥Behind the scenes, actions use the POST method, and only this HTTP method can invoke them.

创建服务器函数

¥Creating Server Functions

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

¥A Server Function can be defined by using the use server directive. You can place the directive at the top of an asynchronous function to mark the function as a Server Function, or at the top of a separate file to mark all exports of that file.

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
}

服务器组件

¥Server Components

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

¥Server Functions can be inlined in Server Components by adding the "use server" directive to the top of the function body:

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

  return <></>
}

需要了解:服务器组件默认支持渐进式增强,这意味着即使 JavaScript 尚未加载或被禁用,调用服务器操作的表单也会被提交。

¥Good to know: Server Components support progressive enhancement by default, meaning forms that call Server Actions will be submitted even if JavaScript hasn't loaded yet or is disabled.

客户端组件

¥Client Components

无法在客户端组件中定义服务器函数。但是,你可以通过从顶部包含 "use server" 指令的文件中导入它们来在客户端组件中调用它们:

¥It's not possible to define Server Functions in Client Components. However, you can invoke them in Client Components by importing them from a file that has the "use server" directive at the top of it:

ts
'use server'

export async function createPost() {}
tsx
'use client'

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

export function Button() {
  return <button formAction={createPost}>Create</button>
}

需要了解:在客户端组件中,如果 JavaScript 尚未加载,调用服务器操作的表单将对提交进行排队,并优先进行数据同步。水合后,浏览器在提交表单时不会刷新。

¥Good to know: In Client Components, forms invoking Server Actions will queue submissions if JavaScript isn't loaded yet, and will be prioritized for hydration. After hydration, the browser does not refresh on form submission.

将操作作为 props 传递

¥Passing actions as props

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

¥You can also pass an action to a Client Component as a prop:

tsx
'use client'

export default function ClientComponent({
  updateItemAction,
}: {
  updateItemAction: (formData: FormData) => void
}) {
  return <form action={updateItemAction}>{/* ... */}</form>
}

调用服务器函数

¥Invoking Server Functions

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

¥There are two main ways you can invoke a Server Function:

  1. 服务器和客户端组件中的 表单

    ¥Forms in Server and Client Components

  2. 客户端组件中的 事件处理程序useEffect

    ¥Event Handlers and useEffect in Client Components

表单

¥Forms

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

¥React extends the HTML <form> element to allow Server Function to be invoked with the HTML action prop.

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

¥When invoked in a form, the function automatically receives the FormData object. You can extract the data using the native FormData methods:

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>
  )
}
ts
'use server'

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

  // Update data
  // Revalidate cache
}

事件处理程序

¥Event Handlers

你可以使用事件处理程序(例如 onClick)在客户端组件中调用服务器函数。

¥You can invoke a Server Function in a Client Component by using event handlers such as onClick.

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>
    </>
  )
}

示例

¥Examples

显示待处理状态

¥Showing a pending state

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

¥While executing a Server Function, you can show a loading indicator with React's useActionState hook. This hook returns a pending boolean:

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>
  )
}

重新验证

¥Revalidating

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

¥After performing an update, you can revalidate the Next.js cache and show the updated data by calling revalidatePath or revalidateTag within the Server Function:

ts
import { revalidatePath } from 'next/cache'

export async function createPost(formData: FormData) {
  'use server'
  // Update data
  // ...

  revalidatePath('/posts')
}

重定向

¥Redirecting

你可能希望在执行更新后将用户重定向到其他页面。你可以通过在服务器函数中调用 redirect 来执行此操作:

¥You may want to redirect the user to a different page after performing an update. You can do this by calling redirect within the Server Function:

ts
'use server'

import { redirect } from 'next/navigation'

export async function createPost(formData: FormData) {
  // Update data
  // ...

  redirect('/posts')
}

Cookies

你可以使用 cookies API 在服务器操作中设置 getsetdelete cookie:

¥You can get, set, and delete cookies inside a Server Action using the cookies API:

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 用于应用快捷方式,用于无限滚动的交叉观察者钩子,或者当组件安装以更新视图计数时:

¥You can use the React useEffect hook to invoke a Server Action when the component mounts or a dependency changes. This is useful for mutations that depend on global events or need to be triggered automatically. For example, onKeyDown for app shortcuts, an intersection observer hook for infinite scrolling, or when the component mounts to update a 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>
}