سرور اکشن در Next.js به ما این امکان را میدهد تا data mutation و مدیریت دادههای asynchronous را به شکلی مؤثر انجام دهیم. گاهی لازم است یک تابع asynchronous مستقل روی سرور اجرا کنیم تا کارهایی مثل ذخیره داده در دیتابیس، ارسال ایمیل، دانلود PDF، پردازش تصاویر و غیره را انجام دهیم.
Next.js Server Actions را ارائه میدهد که توابع asynchronous هستند و روی سرور اجرا میشوند. ما میتوانیم از سرور اکشنها برای data mutationها در سرور استفاده کنیم، اما این اکشنها میتوانند هم از سرور کامپوننتها و هم از کلاینت کامپوننتها فراخوانی شوند.
استفاده از سرور اکشن یک راه حل عالی برای مدیریت ارسال فرمها در Next.js هستند، چرا که اکشن زمانی اجرا میشود که دادههای فرم ارسال شوند. در این مقاله قصد داریم تا به بررسی یک کاربرد عملی از مدیریت آرگومانهای اضافی در سرور اکشن Next.js بپردازیم.
هنگامی که یک سرور اکشن روی ارسال فرم اجرا میشود، دادههای فرم به صورت خودکار به سرور اکشن ارسال میگردند. بهعنوان مثال، فرم زیر را در نظر میگیریم:
<form className="p-4 flex" action={updateUser}>
<Input className="w-1/2 mx-2" type="text" name="name" />
<Button type="submit">Update User Name</Button>
</form>
در این مثال، وقتی فرم ارسال میشود، سرور اکشنی به نام updateUser اجرا میگردد. تابع updateUser دادههای ارسال شده فرم را به عنوان آرگومان دریافت میکند، که میتوانیم از آن برای استخراج مقادیر فیلدهای فرم استفاده کنیم.
همانطور که در قطعه کد زیر مشاهده میکنیم، تابع updateUser آرگومانی به نام formData دریافت میکند و میتوانیم مقدار فیلد name را از آن استخراج نماییم.
"use server"
export async function updateUser(formData) {
const name = formData.get('name');
console.log(name);
}
این الگو بیشتر موارد استفاده بیسیک را پوشش میدهد، اما گاهی باید آرگومانهای اضافی را به صورت برنامهریزی شده به سرور اکشنها ارسال کنیم. این آرگومانها بخشی از فرم، دادههای فرم یا ورودیهای کاربر نیستند؛ بلکه، مقادیری هستند که به صورت برنامهریزی شده به سرور اکشن ما ارسال میشوند.
برای درک بهتر این موضوع، قطعه کد سرور اکشن زیر را بررسی میکنیم. این همان سرور اکشنی است که قبلاً دیدیم، اما در اینجا آرگومان اضافی userId همراه با آرگومان معمولی formData ارسال شده است.
"use server"
export async function updateUser(userId, formData) {
const name = formData.get('name');
console.log(userId);
console.log(name);
}
مقدار userId چیزی داخلی و مرتبط با برنامه است، و ما قصد نداریم از کاربر بخواهیم که این مقدار را به عنوان بخشی از ارسال فرم ارائه دهد. بلکه، ممکن است نیاز باشد این مقدار را به صورت برنامهریزی شده به سرور اکشن خود ارسال کنیم تا محاسبات بیشتری انجام دهیم.
حال که مورد استفاده را درک کردیم، بررسی میکنیم که چگونه میتوانیم این کار را انجام دهیم. برای این کار، یک فرم و یک سرور اکشن کاربردی برای آن میسازیم.
ابتدا، یک پوشه به نام actions در داخل پوشه app پروژه Next.js خود ایجاد میکنیم. سپس، یک فایل به نام user.js داخل پوشه actions میسازیم و کد زیر را در آن قرار میدهیم:
"use server"
export async function updateUser(formData) {
const name = formData.get('name');
console.log(name);
// Do anything with the name, save in DB, create invoice, whatever!
}
این همان روش در Next.js است که به کمک آن یک تابع سرور ایجاد میکنیم. باید در بالای فایل، یک دستور ”use server” قرار دهیم تا به Next.js بگوییم که این یک فایل ویژه است که شامل یک یا چند تابع asynchronous میباشد که روی سرور اجرا میشوند.
سپس، یک سرور اکشن به نام updateUser داریم که آرگومانی به نام formData میگیرد. در داخل این تابع، مقدار فیلد name استخراج شده و در کنسول چاپ میشود.
اکنون این سرور اکشن را به یک فرم متصل میکنیم. برای این کار، یک پوشه به نام components در پوشهی root پروژه میسازیم. پس از آن، یک فایل به نام user-form.jsx ایجاد کرده و کد زیر را در آن قرار میدهیم:
import { Input } from "./ui/input"
import { Button } from "./ui/button"
import { updateUser } from "@/app/actions/user"
const UserForm = () => {
return(
<form className="p-4 flex" action={updateUser}>
<Input className="w-1/2 mx-2" type="text" name="name" />
<Button type="submit">Update User Name</Button>
</form>
)
}
export default UserForm;
این یک کامپوننت ساده React است که شامل یک فرم میباشد. فرم یک فیلد متنی به نام name و یک دکمه برای ارسال دارد. ویژگی action فرم به سرور اکشن updateUser اشاره میکند. حالا وقتی فرم با مقدار name ارسال میشود، همانطور که قبلاً گفتیم، سرور اکشن آن را به عنوان بخشی از دادههای فرم دریافت میکند.
برای تست این فرم، یک route و یک page جدید در Next.js ایجاد میکنیم. ابتدا، یک پوشه به نام extra-args در دایرکتوری app میسازیم. سپس، یک فایل به نام page.js در پوشه app/extra-args ایجاد کرده و کد زیر را در آن قرار میدهیم:
import UserForm from "@/components/user-form";
const ExtraArgsDemo = () => {
return (
<UserForm />
)
}
export default ExtraArgsDemo;
این یک کامپوننت ساده React است که در آن کامپوننت UserForm را import کرده و در JSX مورد استفاده قرار میدهیم. اکنون سرور لوکال را اجرا کرده و مسیر localhost:3000/extra-args را در مرورگر خود باز میکنیم. باید فرمی با یک فیلد متنی و یک دکمه مشاهده نماییم.
یک متن در فیلد تایپ کرده و روی دکمه کلیک میکنیم.
اکنون میتوانیم متن تایپ شده را در کنسول سرور ببینیم. اما چرا این متن در کنسول سرور نمایش داده میشود و نه در کنسول مرورگر؟ این به این دلیل است که سرور اکشنها فقط روی سرور اجرا میشوند، نه در مرورگر کلاینت.
حال یک جریان دادهای به این شکل ایجاد کردهایم:
Page => Form => Server Action
page شامل یک فرم است. فرم در هنگام ارسال، یک سرور اکشن را اجرا میکند. سرور اکشن دادههای فرم را در کنسول سرور چاپ میکند.
در ادامه مقاله قصد داریم تا این بخشها را بهبود دهیم تا بتوانیم آرگومانهای اضافی را به سرور اکشن ارسال کنیم.
اکنون میخواهیم یک prop به نام userId را از page به کامپوننت UserForm ارسال کنیم. این prop به عنوان یک مقدار مشخص ارسال میشود تا نشان دهد که چگونه میتوانیم این userId را به صورت برنامهریزی شده به فرم و سپس به سرور اکشن ارسال نماییم:
import UserForm from "@/components/user-form";
const ExtraArgsDemo = () => {
return (
<UserForm userId={"1234"} />
)
}
export default ExtraArgsDemo;
در کامپوننت UserForm، prop به نام userId را دریافت میکنیم. حالا باید یک کار ویژه انجام دهیم تا این userId به سرور اکشن updateUser ارسال شود.
جاوااسکریپت متدی به نام bind() دارد که به ما این امکان را میدهد تا یک تابع با آرگومانهای از پیش تنظیم شده بسازیم. این تابع، نسخهای جدید از تابع اصلی است که آرگومانهای اولیه در آن تنظیم شدهاند.
در مثال ما، تابع updateUser از قبل یک آرگومان به نام formData دارد. اکنون میتوانیم از متد bind() استفاده کنیم تا آرگومان اضافی userId را به آن اضافه کرده و یک تابع جدید بسازیم.
const updatedUserWithId = updateUser.bind(null, userId);
اولین آرگومان متد bind()، context است که تابع را به آن متصل میکند. این context تعیین میکند که مقدار this در داخل تابع به چه چیزی اشاره داشته باشد. در اینجا میتوانیم آن را null قرار دهیم، چون قصد تغییر آن را نداریم. سپس، آرگومان جدید userId را ارسال میکنیم.
نکتهای که باید به آن توجه داشته باشیم این است که متد bind() هم در سرور کامپوننتها و هم در کلاینت کامپوننتها قابل استفاده است.
در این بخش نسخه تغییر یافته کامپوننت UserForm در فایل user-form.jsx را مشاهده میکنیم، که مقدار ویژگی action فرم به تابع جدید updatedUserWithId تغییر پیدا کرده است.
import { Input } from "./ui/input"
import { Button } from "./ui/button"
import { updateUser } from "@/app/actions/user"
const UserForm = ({userId}) => {
const updatedUserWithId = updateUser.bind(null, userId);
return(
<form className="p-4 flex" action={updatedUserWithId}>
<Input className="w-1/2 mx-2" type="text" name="name" />
<Button type="submit">Update User Name</Button>
</form>
)
}
export default UserForm;
اکنون سرور اکشن مقدار userId را به عنوان آرگومان دریافت میکند. حال این مقدار را همراه با دیگر دادهها در کنسول چاپ میکنیم.
"use server"
export async function updateUser(userId, formData) {
const name = formData.get('name');
console.log(userId);
console.log(name);
// Do anything with the user id and name, save in DB,
// create invoice, whatever!
}
در این مرحله، اگر فرم را با یک مقدار برای name ارسال کنیم، خواهیم دید که هم مقدار userId و هم مقدار name در کنسول سرور لاگ میشوند. در این حالت، یکی از مقادیر از دادههای فرم گرفته میشود و مقدار دیگر مستقیماً به سرور اکشن ارسال میگردد.
تا این قسمت از مقاله یاد گرفتیم که چگونه میتوانیم آرگومانهای اضافی را همراه با دادههای فرم به سرور اکشن ارسال کنیم.
HTML از نوع ورودی hidden پشتیبانی میکند که میتوانیم از آن برای ارسال دادههای سمت کلاینت به سرور، بدون دریافت ورودی مستقیم از کاربران استفاده کنیم. یعنی ما میتوانستیم مقدار userId را با استفاده از یک فیلد مخفی به این شکل ارسال کنیم:
<form className="p-4 flex" action={updatedUserWithId}>
<Input type="hidden" name="userId" type="1234" />
<Input className="w-1/2 mx-2" type="text" name="name" />
<Button type="submit">Update User Name</Button>
</form>
اما ما از متد bind() استفاده کردیم. دلیل این اتفاق این است که فیلدهای hidden نگرانیهای امنیتی دارند. وقتی دادهها را از طریق فیلد hidden ارسال میکنیم، HTML رندر شده شامل مقدار آن میشود و این مقدار به طور پیشفرض رمزگذاری نمیگردد. به همین دلیل، بهتر است دادهها را به صورت برنامهریزی شده مدیریت کنیم تا امنیت بیشتری داشته باشند.
همچنین، از طریق این لینک میتوانیم به فایل کامل پروژه دسترسی داشته باشیم.
در این مقاله یاد گرفتیم که سرور اکشن در Next.js چگونه به ما این امکان را میدهد تا عملیات asynchronous مانند ذخیره دادهها، ارسال ایمیلها و پردازش فرمها را به راحتی روی سرور اجرا کنیم. همچنین دریافتیم که چگونه میتوانیم علاوه بر دادههای فرم، پارامترهای اضافی را به این اکشنها ارسال کنیم.
ما در این مقاله از متد bind() استفاده کردیم که کمک کرد دادههای برنامهریزی شده مثل userId را به صورت ایمن و بدون افشای آنها در HTML ارسال کنیم. این روش نسبت به استفاده از فیلدهای hidden امنتر است و کنترل بیشتری روی دادهها به ما میدهد.
در نهایت، با مثالهای ساده و عملی دیدیم که چطور میتوانیم این قابلیتها را در پروژههای واقعی پیادهسازی کرده و از قدرت سرور اکشن در Next.js بهرهمند شویم.
دیدگاهها: