如何使用伺服器 Action 建立表單
React 伺服器 Action 是在伺服器上執行的伺服器函式。它們可以在伺服器和客戶端元件中呼叫,以處理表單提交。本指南將引導您瞭解如何在 Next.js 中使用伺服器 Action 建立表單。
工作原理
React 擴充套件了 HTML <form> 元素,允許透過 action 屬性呼叫伺服器 Action。
在表單中使用時,該函式會自動接收 FormData 物件。然後,您可以使用原生 FormData 方法 提取資料。
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>
}須知:處理包含多個欄位的表單時,請使用 JavaScript 的
Object.fromEntries()。例如:const rawFormData = Object.fromEntries(formData)。請注意,此物件將包含以$ACTION_為字首的額外屬性。
傳遞附加引數
除了表單欄位,您還可以使用 JavaScript 的 bind 方法向伺服器函式傳遞附加引數。例如,要將 userId 引數傳遞給 updateUser 伺服器函式,可以這樣做:
'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 作為附加引數接收。
'use server'
export async function updateUser(userId: string, formData: FormData) {}須知:
- 另一種方法是將引數作為隱藏輸入欄位傳遞到表單中(例如
<input type="hidden" name="userId" value={userId} />)。但是,該值將成為渲染 HTML 的一部分,並且不會被編碼。bind適用於伺服器和客戶端元件,並支援漸進增強。
表單驗證
表單可以在客戶端或伺服器端進行驗證。
- 對於客戶端驗證,您可以使用 HTML 屬性(如
required和type="email")進行基本驗證。 - 對於伺服器端驗證,您可以使用像 zod 這樣的庫來驗證表單欄位。例如:
'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
}驗證錯誤
要顯示驗證錯誤或訊息,請將定義 <form> 的元件轉換為客戶端元件,並使用 React useActionState。
使用 useActionState 時,伺服器函式的簽名將更改,以接收一個新的 prevState 或 initialState 引數作為其第一個引數。
'use server'
import { z } from 'zod'
export async function createUser(initialState: any, formData: FormData) {
const validatedFields = schema.safeParse({
email: formData.get('email'),
})
// ...
}然後,您可以根據 state 物件有條件地渲染錯誤訊息。
'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>
)
}待定狀態
useActionState 鉤子暴露了一個 pending 布林值,可用於在 Action 執行期間顯示載入指示器或停用提交按鈕。
'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 鉤子在 Action 執行期間顯示載入指示器。使用此鉤子時,您需要建立一個單獨的元件來渲染載入指示器。例如,要在 Action 待定時停用按鈕:
'use client'
import { useFormStatus } from 'react-dom'
export function SubmitButton() {
const { pending } = useFormStatus()
return (
<button disabled={pending} type="submit">
Sign Up
</button>
)
}然後,您可以將 SubmitButton 元件巢狀在表單內部。
import { SubmitButton } from './button'
import { createUser } from '@/app/actions'
export function Signup() {
return (
<form action={createUser}>
{/* Other form elements */}
<SubmitButton />
</form>
)
}須知:在 React 19 中,
useFormStatus返回的物件上包含附加鍵,例如 data、method 和 action。如果您未使用 React 19,則只有pending鍵可用。
樂觀更新
您可以使用 React useOptimistic 鉤子在伺服器函式執行完成之前樂觀地更新 UI,而不是等待響應。
'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>
)
}巢狀表單元素
您可以在巢狀在 <form> 內部的元素中呼叫伺服器 Action,例如 <button>、<input type="submit"> 和 <input type="image">。這些元素接受 formAction 屬性或事件處理程式。
這在您希望在表單中呼叫多個伺服器 Action 的情況下非常有用。例如,除了釋出帖子之外,您還可以建立一個特定的 <button> 元素來儲存帖子草稿。有關更多資訊,請參閱 React <form> 文件。
程式化表單提交
您可以使用 requestSubmit() 方法以程式設計方式觸發表單提交。例如,當用戶使用 ⌘ + Enter 鍵盤快捷鍵提交表單時,您可以監聽 onKeyDown 事件:
'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> 祖先的提交,從而呼叫伺服器函式。
這有幫助嗎?