سرور اکشن در 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 بهرهمند شویم.
دیدگاهها: