您可以使用 React 的 Server Functions 在 Next.js 中更新数据。本页将介绍如何创建和调用 Server Functions。
Server Function 是一个在服务器上运行的异步函数。它们可以通过网络请求从客户端调用,这也是它们必须是异步的原因。
在 action 或 mutation 上下文中,它们也被称为 Server Actions。
按照惯例,Server Action 是一个与 startTransition 搭配使用的异步函数。当函数满足以下条件时,这会自动发生:
action prop 传递给 <form>。formAction prop 传递给 <button>。在 Next.js 中,Server Actions 与框架的缓存架构集成。当 action 被调用时,Next.js 可以在单个服务器往返中返回更新后的 UI 和新数据。
在底层,actions 使用 POST 方法,并且只有这种 HTTP 方法才能调用它们。
Server Function 可以通过使用 use server 指令来定义。您可以将该指令放置在异步函数的顶部以将其标记为 Server Function,或者放置在独立文件的顶部以标记该文件的所有导出。
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
}export async function createPost(formData) {
'use server'
const title = formData.get('title')
const content = formData.get('content')
// Update data
// Revalidate cache
}
export async function deletePost(formData) {
'use server'
const id = formData.get('id')
// Update data
// Revalidate cache
}Server Functions 可以通过在函数体顶部添加 "use server" 指令来内联在 Server Components 中:
export default function Page() {
// Server Action
async function createPost(formData: FormData) {
'use server'
// ...
}
return <></>
}export default function Page() {
// Server Action
async function createPost(formData) {
'use server'
// ...
}
return <></>
}值得注意的是: Server Components 默认支持渐进式增强,这意味着即使 JavaScript 尚未加载或已被禁用,调用 Server Actions 的表单也将提交。
无法在 Client Components 中定义 Server Functions。但是,您可以通过从一个顶部带有 "use server" 指令的文件中导入它们来在 Client Components 中调用它们:
'use server'
export async function createPost() {}'use server'
export async function createPost() {}'use client'
import { createPost } from '@/app/actions'
export function Button() {
return <button formAction={createPost}>Create</button>
}'use client'
import { createPost } from '@/app/actions'
export function Button() {
return <button formAction={createPost}>Create</button>
}值得注意的是: 在 Client Components 中,如果 JavaScript 尚未加载,调用 Server Actions 的表单将排队提交,并且将优先进行水合(hydration)。水合后,浏览器在表单提交时不会刷新。
您也可以将 action 作为 prop 传递给 Client Component:
<ClientComponent updateItemAction={updateItem} />'use client'
export default function ClientComponent({
updateItemAction,
}: {
updateItemAction: (formData: FormData) => void
}) {
return <form action={updateItemAction}>{/* ... */}</form>
}'use client'
export default function ClientComponent({ updateItemAction }) {
return <form action={updateItemAction}>{/* ... */}</form>
}有两种主要方式来调用 Server Function:
值得注意的是: Server Functions 专为服务器端 mutations 设计。客户端目前会逐个分派并等待它们。这是一个实现细节,可能会发生变化。如果您需要并行数据获取,请在 Server Components 中使用数据获取,或者在单个 Server Function 或 Route Handler 内部执行并行工作。
React 扩展了 HTML <form> 元素,允许通过 HTML 的 action prop 调用 Server Function。
在表单中调用时,该函数会自动接收 FormData 对象。您可以使用原生 FormData 方法提取数据:
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>
)
}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>
)
}'use server'
export async function createPost(formData: FormData) {
const title = formData.get('title')
const content = formData.get('content')
// Update data
// Revalidate cache
}'use server'
export async function createPost(formData) {
const title = formData.get('title')
const content = formData.get('content')
// Update data
// Revalidate cache
}您可以通过使用 onClick 等事件处理程序在 Client Component 中调用 Server Function。
'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>
</>
)
}'use client'
import { incrementLike } from './actions'
import { useState } from 'react'
export default function LikeButton({ initialLikes }) {
const [likes, setLikes] = useState(initialLikes)
return (
<>
<p>Total Likes: {likes}</p>
<button
onClick={async () => {
const updatedLikes = await incrementLike()
setLikes(updatedLikes)
}}
>
Like
</button>
</>
)
}在执行 Server Function 时,您可以使用 React 的 useActionState Hook 来显示加载指示器。此 Hook 返回一个 pending 布尔值:
'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>
)
}'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>
)
}在 mutation 后,您可能希望刷新当前页面以显示最新数据。您可以通过在 Server Action 中调用 next/cache 中的 refresh 来实现:
'use server'
import { refresh } from 'next/cache'
export async function updatePost(formData: FormData) {
// Update data
// ...
refresh()
}'use server'
import { refresh } from 'next/cache'
export async function updatePost(formData) {
// Update data
// ...
refresh()
}这会刷新客户端路由器,确保 UI 反映最新状态。refresh() 函数不会重新验证带标签的数据。要重新验证带标签的数据,请改用 updateTag 或 revalidateTag。
执行更新后,您可以通过在 Server Function 内部调用 revalidatePath 或 revalidateTag 来重新验证 Next.js 缓存并显示更新后的数据:
import { revalidatePath } from 'next/cache'
export async function createPost(formData: FormData) {
'use server'
// Update data
// ...
revalidatePath('/posts')
}import { revalidatePath } from 'next/cache'
export async function createPost(formData) {
'use server'
// Update data
// ...
revalidatePath('/posts')
}执行更新后,您可能希望将用户重定向到不同的页面。您可以通过在 Server Function 中调用 redirect 来实现。
'use server'
import { revalidatePath } from 'next/cache'
import { redirect } from 'next/navigation'
export async function createPost(formData: FormData) {
// Update data
// ...
revalidatePath('/posts')
redirect('/posts')
}'use server'
import { revalidatePath } from 'next/cache'
import { redirect } from 'next/navigation'
export async function createPost(formData) {
// Update data
// ...
revalidatePath('/posts')
redirect('/posts')
}调用 redirect 会抛出一个由框架处理的控制流异常。其后的任何代码都不会执行。如果需要获取最新数据,请在此之前调用 revalidatePath 或 revalidateTag。
您可以在 Server Action 内部使用 cookies API 来 get、set 和 delete cookie。
当您在 Server Action 中设置或删除 cookie 时,Next.js 会在服务器上重新渲染当前页面及其布局,以便 UI 反映新的 cookie 值。
值得注意的是:服务器更新适用于当前的 React 树,根据需要重新渲染、挂载或卸载组件。客户端状态对于重新渲染的组件会保留,并且如果依赖项发生变化,effects 会重新运行。
'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')
}'use server'
import { cookies } from 'next/headers'
export async function exampleAction() {
// Get cookie
const cookieStore = await cookies()
// Get cookie
cookieStore.get('name')?.value
// Set cookie
cookieStore.set('name', 'Delba')
// Delete cookie
cookieStore.delete('name')
}您可以使用 React 的 useEffect Hook 来在组件挂载时或依赖项发生变化时调用 Server Action。这对于依赖全局事件或需要自动触发的 mutations 非常有用。例如,用于应用程序快捷方式的 onKeyDown、用于无限滚动的交叉观察器 Hook,或者在组件挂载时更新浏览计数:
'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>
}'use client'
import { incrementViews } from './actions'
import { useState, useEffect, useTransition } from 'react'
export default function ViewCount({ initialViews }) {
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>
}