表单
¥Forms
React 服务器操作是在服务器上执行的 服务器函数。它们可以在服务器和客户端组件中调用以处理表单提交。本指南将引导你了解如何使用服务器操作在 Next.js 中创建表单。
¥React Server Actions are Server Functions that execute on the server. They can be called in Server and Client Components to handle form submissions. This guide will walk you through how to create forms in Next.js with Server Actions.
怎么运行的
¥How it works
React 扩展了 HTML <form>
元素,允许使用 action
属性调用服务器操作。
¥React extends the HTML <form>
element to allow Server Actions to be invoked with the action
attribute.
在表单中使用时,该函数会自动接收 FormData
对象。然后,你可以使用原生 FormData
方法 提取数据:
¥When used in a form, the function automatically receives the FormData
object. You can then extract the data using the native FormData
methods:
export default function Page() {
async function createInvoice(formData: FormData) {
'use server'
const rawFormData = {
customerId: formData.get('customerId'),
amount: formData.get('amount'),
status: formData.get('status'),
}
// mutate data
// revalidate the cache
}
return <form action={createInvoice}>...</form>
}
需要了解:处理包含多个字段的表单时,可以将
entries()
方法与 JavaScript 的Object.fromEntries()
结合使用。例如:const rawFormData = Object.fromEntries(formData)
。¥Good to know: When working with forms that have multiple fields, you can use the
entries()
method with JavaScript'sObject.fromEntries()
. For example:const rawFormData = Object.fromEntries(formData)
.
传递其他参数
¥Passing additional arguments
在表单字段之外,你可以使用 JavaScript bind
方法向服务器函数传递其他参数。例如,将 userId
参数传递给 updateUser
服务器函数:
¥Outside of form fields, you can pass additional arguments to a Server Function using the JavaScript bind
method. For example, to pass the userId
argument to the updateUser
Server Function:
'use client'
import { updateUser } from './actions'
export function UserProfile({ userId }: { userId: string }) {
const updateUserWithId = updateUser.bind(null, userId)
return (
<form action={updateUserWithId}>
<input type="text" name="name" />
<button type="submit">Update User Name</button>
</form>
)
}
服务器函数将接收 userId
作为附加参数:
¥The Server Function will receive the userId
as an additional argument:
'use server'
export async function updateUser(userId: string, formData: FormData) {}
需要了解:
¥Good to know:
另一种方法是将参数作为表单中的隐藏输入字段传递(例如
<input type="hidden" name="userId" value={userId} />
)。但是,该值将成为渲染的 HTML 的一部分,并且不会被编码。¥An alternative is to pass arguments as hidden input fields in the form (e.g.
<input type="hidden" name="userId" value={userId} />
). However, the value will be part of the rendered HTML and will not be encoded.
bind
可在服务器和客户端组件中运行,并支持渐进式增强。¥
bind
works in both Server and Client Components and supports progressive enhancement.
表单验证
¥Form validation
表单可以在客户端或服务器上进行验证。
¥Forms can be validated on the client or server.
对于客户端验证,你可以使用 HTML 属性(例如
required
和type="email"
)进行基本验证。¥For client-side validation, you can use the HTML attributes like
required
andtype="email"
for basic validation.对于服务器端验证,你可以使用像 zod 这样的库来验证表单字段。例如:
¥For server-side validation, you can use a library like zod to validate the form fields. For example:
'use server'
import { z } from 'zod'
const schema = z.object({
email: z.string({
invalid_type_error: 'Invalid Email',
}),
})
export default async function createUser(formData: FormData) {
const validatedFields = schema.safeParse({
email: formData.get('email'),
})
// Return early if the form data is invalid
if (!validatedFields.success) {
return {
errors: validatedFields.error.flatten().fieldErrors,
}
}
// Mutate data
}
验证错误
¥Validation errors
要显示验证错误或消息,请将定义 <form>
的组件转换为客户端组件并使用 React useActionState
。
¥To display validation errors or messages, turn the component that defines the <form>
into a Client Component and use React useActionState
.
使用 useActionState
时,服务器函数签名将更改为接收新的 prevState
或 initialState
参数作为其第一个参数。
¥When using useActionState
, the Server function signature will change to receive a new prevState
or initialState
parameter as its first argument.
'use server'
import { z } from 'zod'
export async function createUser(initialState: any, formData: FormData) {
const validatedFields = schema.safeParse({
email: formData.get('email'),
})
// ...
}
然后,你可以根据 state
对象有条件地渲染错误消息。
¥You can then conditionally render the error message based on the state
object.
'use client'
import { useActionState } from 'react'
import { createUser } from '@/app/actions'
const initialState = {
message: '',
}
export function Signup() {
const [state, formAction, pending] = useActionState(createUser, initialState)
return (
<form action={formAction}>
<label htmlFor="email">Email</label>
<input type="text" id="email" name="email" required />
{/* ... */}
<p aria-live="polite">{state?.message}</p>
<button disabled={pending}>Sign up</button>
</form>
)
}
待定状态
¥Pending states
useActionState
钩子公开了一个 pending
布尔值,可用于在执行操作时显示加载指示器或禁用提交按钮。
¥The useActionState
hook exposes a pending
boolean that can be used to show a loading indicator or disable the submit button while the action is being executed.
'use client'
import { useActionState } from 'react'
import { createUser } from '@/app/actions'
export function Signup() {
const [state, formAction, pending] = useActionState(createUser, initialState)
return (
<form action={formAction}>
{/* Other form elements */}
<button disabled={pending}>Sign up</button>
</form>
)
}
或者,你可以使用 useFormStatus
钩子在执行操作时显示加载指示器。使用此钩子时,你需要创建一个单独的组件来渲染加载指示器。例如,要在操作待处理时禁用按钮:
¥Alternatively, you can use the useFormStatus
hook to show a loading indicator while the action is being executed. When using this hook, you'll need to create a separate component to render the loading indicator. For example, to disable the button when the action is pending:
'use client'
import { useFormStatus } from 'react-dom'
export function SubmitButton() {
const { pending } = useFormStatus()
return (
<button disabled={pending} type="submit">
Sign Up
</button>
)
}
然后,你可以在表单内嵌套 SubmitButton
组件:
¥You can then nest the SubmitButton
component inside the form:
import { SubmitButton } from './button'
import { createUser } from '@/app/actions'
export function Signup() {
return (
<form action={createUser}>
{/* Other form elements */}
<SubmitButton />
</form>
)
}
需要了解:在 React 19 中,
useFormStatus
在返回的对象上包含其他键,如数据、方法和操作。如果你没有使用 React 19,则只有pending
键可用。¥Good to know: In React 19,
useFormStatus
includes additional keys on the returned object, like data, method, and action. If you are not using React 19, only thepending
key is available.
乐观的更新
¥Optimistic updates
你可以使用 React useOptimistic
hook 在服务器函数执行完成之前乐观地更新 UI,而不是等待响应:
¥You can use the React useOptimistic
hook to optimistically update the UI before the Server Function finishes executing, rather than waiting for the response:
'use client'
import { useOptimistic } from 'react'
import { send } from './actions'
type Message = {
message: string
}
export function Thread({ messages }: { messages: Message[] }) {
const [optimisticMessages, addOptimisticMessage] = useOptimistic<
Message[],
string
>(messages, (state, newMessage) => [...state, { message: newMessage }])
const formAction = async (formData: FormData) => {
const message = formData.get('message') as string
addOptimisticMessage(message)
await send(message)
}
return (
<div>
{optimisticMessages.map((m, i) => (
<div key={i}>{m.message}</div>
))}
<form action={formAction}>
<input type="text" name="message" />
<button type="submit">Send</button>
</form>
</div>
)
}
嵌套表单元素
¥Nested form elements
你可以在嵌套在 <form>
中的元素(例如 <button>
、<input type="submit">
和 <input type="image">
)中调用服务器操作。这些元素接受 formAction
属性或事件处理程序。
¥You can call Server Actions in elements nested inside <form>
such as <button>
, <input type="submit">
, and <input type="image">
. These elements accept the formAction
prop or event handlers.
这在你想要在表单中调用多个服务器操作的情况下非常有用。例如,你可以创建一个特定的 <button>
元素来保存帖子草稿以及发布它。请参阅 React <form>
文档 了解更多信息。
¥This is useful in cases where you want to call multiple Server Actions within a form. For example, you can create a specific <button>
element for saving a post draft in addition to publishing it. See the React <form>
docs for more information.
程序化表单提交
¥Programmatic form submission
你可以使用 requestSubmit()
方法以编程方式触发表单提交。例如,当用户使用 ⌘
+ Enter
键盘快捷键提交表单时,你可以监听 onKeyDown
事件:
¥You can trigger a form submission programmatically using the requestSubmit()
method. For example, when the user submits a form using the ⌘
+ Enter
keyboard shortcut, you can listen for the onKeyDown
event:
'use client'
export function Entry() {
const handleKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
if (
(e.ctrlKey || e.metaKey) &&
(e.key === 'Enter' || e.key === 'NumpadEnter')
) {
e.preventDefault()
e.currentTarget.form?.requestSubmit()
}
}
return (
<div>
<textarea name="entry" rows={20} required onKeyDown={handleKeyDown} />
</div>
)
}
这将触发提交最近的 <form>
祖级,从而调用服务器函数。
¥This will trigger the submission of the nearest <form>
ancestor, which will invoke the Server Function.