跳到主要内容

更新数据

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

什么是服务端函数?

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

action 或修改上下文中,它们也被称为服务端操作

按照约定,服务端操作是与 startTransition 一起使用的异步函数。当函数被以下方式使用时,这会自动发生:

  • 使用 action prop 传递给 <form>
  • 使用 formAction prop 传递给 <button>

在 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')

// 更新数据
// 重新验证缓存
}

export async function deletePost(formData: FormData) {
'use server'
const id = formData.get('id')

// 更新数据
// 重新验证缓存
}

服务端组件

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

app/page.tsx
export default function Page() {
// 服务端操作
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}>添加</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 prop 调用服务端函数。

在表单中调用时,函数会自动接收 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')

// 更新数据
// 重新验证缓存
}

事件处理器

你可以通过在客户端组件中使用事件处理器(如 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>
)
}

重新验证

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

app/lib/actions.ts
import { revalidatePath } from 'next/cache'

export async function createPost(formData: FormData) {
'use server'
// 更新数据
// ...

revalidatePath('/posts')
}
app/actions.js
import { revalidatePath } from 'next/cache'

export async function createPost(formData) {
'use server'
// 更新数据
// ...
revalidatePath('/posts')
}

重定向

你可能希望在执行更新后将用户重定向到不同的页面。你可以通过在服务端函数内调用 redirect 来做到这一点:

app/lib/actions.ts
'use server'

import { redirect } from 'next/navigation'

export async function createPost(formData: FormData) {
// 更新数据
// ...

redirect('/posts')
}
app/actions.js
'use server'

import { redirect } from 'next/navigation'

export async function createPost(formData) {
// 更新数据
// ...

redirect('/posts')
}

Cookies

你可以使用 cookies API 在服务端操作内 getsetdelete cookies:

app/actions.ts
'use server'

import { cookies } from 'next/headers'

export async function exampleAction() {
const cookieStore = await cookies()

// 获取 cookie
cookieStore.get('name')?.value

// 设置 cookie
cookieStore.set('name', 'Delba')

// 删除 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)
})
}, [])

// 你可以使用 `isPending` 给用户反馈
return <p>Total Views: {views}</p>
}