كيفية إنشاء نماذج باستخدام إجراءات الخادم (Server Actions)
إجراءات الخادم (Server Actions) في React هي وظائف الخادم (Server Functions) التي تنفذ على الخادم. يمكن استدعاؤها في مكونات الخادم والعميل (Client Components) لمعالجة إرسال النماذج. سيرشدك هذا الدليل خطوة بخطوة حول كيفية إنشاء نماذج في Next.js باستخدام إجراءات الخادم.
كيفية عملها
تقوم React بتمديد عنصر <form>
في HTML للسماح باستدعاء إجراءات الخادم باستخدام السمة 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>
}
export default function Page() {
async function createInvoice(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()
معObject.fromEntries()
في JavaScript. على سبيل المثال:const rawFormData = Object.fromEntries(formData)
.
تمرير وسائط إضافية
خارج حقول النموذج، يمكنك تمرير وسائط إضافية إلى وظيفة الخادم باستخدام طريقة bind
في JavaScript. على سبيل المثال، لتمرير وسيطة 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>
)
}
'use client'
import { updateUser } from './actions'
export function UserProfile({ userId }) {
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) {}
'use server'
export async function updateUser(userId, 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
}
'use server'
import { z } from 'zod'
const schema = z.object({
email: z.string({
invalid_type_error: 'Invalid Email',
}),
})
export default async function createsUser(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>
إلى مكون عميل (Client Component) واستخدم useActionState
في React.
عند استخدام 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'),
})
// ...
}
'use server'
import { z } from 'zod'
// ...
export async function createUser(initialState, 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>
)
}
'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
يمكن استخدامها لعرض مؤشر تحميل أو تعطيل زر الإرسال أثناء تنفيذ الإجراء.
'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>
)
}
'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
لعرض مؤشر تحميل أثناء تنفيذ الإجراء. عند استخدام هذا الخطاف، ستحتاج إلى إنشاء مكون منفصل لعرض مؤشر التحميل. على سبيل المثال، لتعطيل الزر عندما يكون الإجراء قيد الانتظار:
يمكنك بعد ذلك تضمين مكون SubmitButton
داخل النموذج:
import { SubmitButton } from './button'
import { createUser } from '@/app/actions'
export function Signup() {
return (
<form action={createUser}>
{/* Other form elements */}
<SubmitButton />
</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
مفاتيح إضافية في الكائن المعاد، مثل data وmethod وaction. إذا كنت لا تستخدم React 19، فإن مفتاحpending
فقط هو المتاح.
التحديثات المتفائلة
يمكنك استخدام خطاف React useOptimistic
لتحديث واجهة المستخدم بشكل متفائل قبل انتهاء تنفيذ وظيفة الخادم، بدلاً من انتظار الرد:
'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>
)
}
'use client'
import { useOptimistic } from 'react'
import { send } from './actions'
export function Thread({ messages }) {
const [optimisticMessages, addOptimisticMessage] = useOptimistic(
messages,
(state, newMessage) => [...state, { message: newMessage }]
)
const formAction = async (formData) => {
const message = formData.get('message')
addOptimisticMessage(message)
await send(message)
}
return (
<div>
{optimisticMessages.map((m) => (
<div>{m.message}</div>
))}
<form action={formAction}>
<input type="text" name="message" />
<button type="submit">Send</button>
</form>
</div>
)
}
عناصر النموذج المتداخلة
يمكنك استدعاء إجراءات الخادم في عناصر متداخلة داخل <form>
مثل <button>
و<input type="submit">
و<input type="image">
. تقبل هذه العناصر خاصية formAction
أو معالجات الأحداث.
هذا مفيد في الحالات التي تريد فيها استدعاء إجراءات خادم متعددة داخل نموذج واحد. على سبيل المثال، يمكنك إنشاء عنصر <button>
محدد لحفظ مسودة منشور بالإضافة إلى نشره. راجع وثائق <form>
في React لمزيد من المعلومات.
إرسال النموذج برمجيًا
يمكنك تشغيل إرسال النموذج برمجيًا باستخدام طريقة 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>
)
}
'use client'
export function Entry() {
const handleKeyDown = (e) => {
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>
، مما سيستدعي وظيفة الخادم.