更新数据
你可以使用 React 的服务端函数在 Next.js 中更新数据。本页将介绍如何创建和调用服务端函数。
什么是服务端函数?
服务端函数是在服务器上运行的异步函数。它们可以通过网络请求从客户端调用,这就是为什么它们必须是异步的。
在 action 或修改上下文中,它们也被称为服务端操作。
按照约定,服务端操作是与 startTransition 一起使用的异步函数。当函数被以下方式使用时,这会自动发生:
- 使用
actionprop 传递给<form>。 - 使用
formActionprop 传递给<button>。
在 Next.js 中,服务端操作与框架的缓存架构集成。当操作被调用时,Next.js 可以在单个服务器往返中返回更新的 UI 和新数据。
在幕后,操作使用 POST 方法,只有这个 HTTP 方法可以调用它们。
创建服务端函数
可以通过使用 use server 指令来定义服务端函数。你可以将指令放在异步函数的顶部来标记该函数为服务端函数,或者放在单独文件的顶部来标记该文件的所有导出。
- TypeScript
- JavaScript
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')
// 更新数据
// 重新验证缓存
}
export async function createPost(formData) {
'use server'
const title = formData.get('title')
const content = formData.get('content')
// 更新数据
// 重新验证缓存
}
export async function deletePost(formData) {
'use server'
const id = formData.get('id')
// 更新数据
// 重新验证缓存
}
服务端组件
可以通过在函数体顶部添加 "use server" 指令在服务端组件中内联服务端函数:
- TypeScript
- JavaScript
export default function Page() {
// 服务端操作
async function createPost(formData: FormData) {
'use server'
// ...
}
return <></>
}
export default function Page() {
// 服务端操作
async function createPost(formData: FormData) {
'use server'
// ...
}
return <></>
}
注意: 服务端组件默认支持渐进增强,这意味着调用服务端操作的表单即使在 JavaScript 尚未加载或已禁用的情况下也会被提交。
客户端组件
无法能在客户端组件中定义服务端函数。但是,你可以通过从顶部有 "use server" 指令的文件导入它们来在客户端组件中调用它们:
- TypeScript
- JavaScript
'use server'
export async function createPost() {}
'use server'
export async function createPost() {}
- TypeScript
- JavaScript
'use client'
import { createPost } from '@/app/actions'
export function Button() {
return <button formAction={createPost}>添加</button>
}
'use client'
import { createPost } from '@/app/actions'
export function Button() {
return <button formAction={createPost}>添加</button>
}
注意: 在客户端组件中,调用服务端操作的表单如果 JavaScript 尚未加载,将排队提交,并将优先进行水合。水合后,浏览器在表单提交时不会刷新。
将操作作为 props 传递
你也可以将操作作为 prop 传递给客户端组件:
<ClientComponent updateItemAction={updateItem} />
- TypeScript
- JavaScript
'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>
}
调用服务端函数
有两种主要方式可以调用服务端函数:
表单
React 扩展了 HTML <form> 元素,允许使用 HTML action prop 调用服务端函数。
在表单中调用时,函数会自动接收 FormData 对象。你可以使用原生 FormData 方法提取数据:
- TypeScript
- JavaScript
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>
)
}
- TypeScript
- JavaScript
'use server'
export async function createPost(formData: FormData) {
const title = formData.get('title')
const content = formData.get('content')
// 更新数据
// 重新验证缓存
}
'use server'
export async function createPost(formData) {
const title = formData.get('title')
const content = formData.get('content')
// 更新数据
// 重新验证缓存
}
事件处理器
你可以通过在客户端组件中使用事件处理器(如 onClick)来调用服务端函数。
- TypeScript
- JavaScript
'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>
</>
)
}
示例
显示待处理状态
在执行服 务端函数时,你可以使用 React 的 useActionState 钩子显示加载指示器。这个钩子返回一个 pending 布尔值:
- TypeScript
- JavaScript
'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>
)
}
重新验证
执行更新后,你可以通过在服务端函数内调用 revalidatePath 或 revalidateTag 来重新验证 Next.js 缓存并显示更新的数据:
- TypeScript
import { revalidatePath } from 'next/cache'
export async function createPost(formData: FormData) {
'use server'
// 更新数据
// ...
revalidatePath('/posts')
}
- JavaScript
import { revalidatePath } from 'next/cache'
export async function createPost(formData) {
'use server'
// 更新数据
// ...
revalidatePath('/posts')
}
重定向
你可能希望在执行更新后将用户重定向到不同的页面。你可以通过在服务端函数内调用 redirect 来做到这一点:
- TypeScript
'use server'
import { redirect } from 'next/navigation'
export async function createPost(formData: FormData) {
// 更新数据
// ...
redirect('/posts')
}
- JavaScript
'use server'
import { redirect } from 'next/navigation'
export async function createPost(formData) {
// 更新数据
// ...
redirect('/posts')
}
Cookies
你可以使用 cookies API 在服务端操作内 get、set 和 delete cookies:
- TypeScript
- JavaScript
'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')
}
'use server'
import { cookies } from 'next/headers'
export async function exampleAction() {
// 获取 cookie
const cookieStore = await cookies()
// 获取 cookie
cookieStore.get('name')?.value
// 设置 cookie
cookieStore.set('name', 'Delba')
// 删除 cookie
cookieStore.delete('name')
}
useEffect
你可以使用 React useEffect 钩子在组件挂载或依赖项更改时调用服务端操作。这对于依赖于全局事件或需要自动触发的修改很有用。例如,应用程序快捷键的 onKeyDown、无限滚动的交叉观察器钩子,或者在组件挂载时更新视图计数:
- TypeScript
- JavaScript
'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>
}
'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)
})
}, [])
// 你可以使用 `isPending` 给用户反馈
return <p>Total Views: {views}</p>
}