وقتی یک تعامل یا اینترکشن را در UI طراحی میکنید، احتمالاً به این فکر میکنید که واجهه کاربری در پاسخ به اقدامات کاربر چطور تغییر میکند. فرمی را در نظر بگیرید که کاربر میتواند پاسخی را در آن سابمیت کند:
وقتی چیزی در فرم تایپ میکنید، دکمه «ثبت» فعال میشود.
وقتی دکمه «ثبت» را میفشارید، فرم و دکمه غیرفعال شده و یک اسپینر (لودینگ) ظاهر میشود.
اگر درخواست شبکه موفقیتآمیز باشد، فرم پنهان شده و پیام «تشکر» نمایش داده میشود.
اگر درخواست شبکه با خطا مواجه شود، پیام خطا ظاهر شده و فرم دوباره فعال میشود.
در برنامهنویسی دستوری (Imperative)، سناریوی بالا دقیقاً همان روشی است که شما کد را پیادهسازی میکنید. شما باید دستورات صریح و گامبهگامی بنویسید تا بسته به اتفاقی که افتاده، DOM را دستکاری کند.
برای درک بهتر، تصور کنید کنار راننده در ماشین نشستهاید و پیچبهپیک به او میگویید به کدام سمت برود. راننده مقصد را نمیداند، او فقط دستورات لحظهای شما را اجرا میکند (و اگر یکی از آدرسها را اشتباه بگویید، سر از جای اشتباهی در میآورید!). به این دلیل به آن دستوری میگویند چون شما باید به تکتک المانها — از اسپینر گرفته تا دکمه — «دستور» بدهید که چه زمانی خود را غیب یا ظاهر کنند.
دستکاری دستوری UI در مثالهای کوچک و منزوی خوب کار میکند، اما در سیستمهای پیچیده مدیریت آن به شدت دشوار و مستعد باگ میشود. اگر صفحهای پر از فرمهای مختلف داشته باشید، اضافه کردن یک ویژگی جدید نیازمند بررسی دقیق تمام کدهای قبلی است تا مطمئن شوید پنهان یا آشکار کردن المانی را فراموش نکردهاید.
ریآکت (React) دقیقاً برای حل این مشکل ساخته شده است.
در ریآکت، شما به طور مستقیم UI را دستکاری نمیکنید؛ یعنی مستقیماً المانها را فعال، غیرفعال، پنهان یا آشکار نمیکنید. در عوض، شما اعلام (Declare) میکنید که میخواهید چه چیزی روی صفحه دیده شود و ریآکت خودش میفهمد چطور UI را بهروزرسانی کند. این بار تصور کنید سوار تاکسی شدهاید و فقط مقصد را به راننده میگویید. این وظیفه راننده است که چطور شما را به مقصد برساند.
برای بازنویسی یک ایده به روش ریآکت، باید این ۵ گام طلایی را طی کنید:
در علوم کامپیوتر با مفهوم «ماشین وضعیت» (State Machine) آشنا هستید. طراحان UI نیز لایههای مختلف یک طرح را بر اساس وضعیتهای بصری دیزاین میکنند. ابتدا تمام حالتهایی که کاربر ممکن است روی صفحه ببیند را فهرست کنید:
Empty (خالی): فرم قفل است و دکمه ثبت غیرفعال است.
Typing (در حال تایپ): کاربر متنی وارد کرده و دکمه ثبت فعال شده است.
Submitting (در حال ارسال): فرم کاملاً قفل شده و اسپینر در حال چرخش است.
Success (موفقیت): فرم غیب شده و پیام تشکر جای آن را گرفته است.
Error (خطا): مشابه وضعیت Typing، اما یک پیام خطای قرمز رنگ هم دیده میشود.
قبل از نوشتن هرگونه منطق (Logic)، میتوانید با یک پروپ فرضی به نام status وضعیتهای بصری خود را مانند یک طرح اولیه (Mockup) پیادهسازی کنید تا از ظاهر کار مطمئن شوید:
export default function Form({ status = 'empty' }) {
if (status === 'success') {
return <h1>پاسخ شما درست بود!</h1>
}
return (
<>
<h2>مسابقه شهرشناسی</h2>
<form>
<textarea disabled={status === 'submitting'} />
<br />
<button disabled={status === 'empty' || status === 'submitting'}>
ثبت پاسخ
</button>
{status === 'error' && <p className="Error">پاسخ اشتباه بود. دوباره تلاش کنید!</p>}
</form>
</>
);
}
تغییرات استیت در برنامه توسط دو نوع ورودی تحریک میشوند: ۱. ورودیهای انسانی (Human Inputs): مثل کلیک روی دکمه، تایپ در فیلد، یا کلیک روی یک لینک. ۲. ورودیهای کامپیوتری (Computer Inputs): مثل رسیدن پاسخ از شبکه، اتمام زمان تایماوت، یا لود شدن یک تصویر.
برای این فرم، جریان تغییرات به شکل زیر است:
تغییر متن در تکستاریا (انسانی) → انتقال بین وضعیت Empty و Typing.
کلیک روی دکمه ثبت (انسانی) → انتقال به وضعیت Submitting.
دریافت پاسخ موفقیت از سرور (کامپیوتری) → انتقال به وضعیت Success.
دریافت خطای شبکه (کامپیوتری) → انتقال به وضعیت Error.
useStateحالا باید وضعیتهای خود را در قالب استیتهای ریآکت تعریف کنید. سادگی کلید موفقیت است؛ هرچه متغیرهای متحرک کمتری داشته باشید، باگ کمتری خواهید داشت. ابتدا با متغیرهایی که قطعاً به آنها نیاز دارید شروع کنید:
const [answer, setAnswer] = useState('');
const [error, setError] = useState(null);
سپس ممکن است در نگاه اول برای تکتک وضعیتهای بصری یک بولین بسازید:
// استیتهای اولیه (که بعداً بازنویسی و بهینه میشوند)
const [isEmpty, setIsEmpty] = useState(true);
const [isTyping, setIsTyping] = useState(false);
const [isSubmitting, setIsSubmitting] = useState(false);
const [isSuccess, setIsSuccess] = useState(false);
const [isError, setIsError] = useState(false);
هدف ما در این گام، جلوگیری از ایجاد وضعیتهای غیرممکن (Impossible States) در حافظه است. به عنوان مثال، هیچوقت نباید دو استیت isTyping و isSubmitting همزمان true باشند، چون چنین حالتی در دنیای واقعی وجود ندارد و باعث بروز باگ تداخل میشود.
سؤالاتی که باید برای پاکسازی استیتها از خود بپرسید:
آیا این استیتها باعث تناقض میشوند؟ بله، isTyping و isSubmitting متناقض هستند. پس آنها را ترکیب کرده و به یک استیت رشتهای واحد به نام status تبدیل میکنیم که فقط میتواند یکی از مقادیر 'typing'، 'submitting' یا 'success' را داشته باشد.
آیا این اطلاعات از قبل در استیت دیگری وجود دارد؟ استیت isEmpty نیاز نیست، چون هر زمان بخواهیم میتوانیم مقدار answer.length === 0 را بررسی کنیم.
آیا میتوان این استیت را از برعکس کردن یک استیت دیگر به دست آورد؟ استیت isError اضافی است، چون داشتن خطا یعنی error !== null.
پس از این خانهتکانی، از ۷ متغیر استیت به ۳ متغیر حیاتی میرسیم:
const [answer, setAnswer] = useState('');
const [error, setError] = useState(null);
const [status, setStatus] = useState('typing'); // 'typing', 'submitting', or 'success'
در نهایت، کدهای رویداد را مینویسیم تا این استیتها را مدیریت کنند. نسخه نهایی و کامل کامپوننت به شکل زیر خواهد بود:
import { useState } from 'react';
export default function Form() {
const [answer, setAnswer] = useState('');
const [error, setError] = useState(null);
const [status, setStatus] = useState('typing'); // 'typing', 'submitting', 'success'
if (status === 'success') {
return <h1>پاسخ شما درست بود!</h1>
}
async function handleSubmit(e: React.FormEvent) {
e.preventDefault();
setStatus('submitting');
try {
await submitForm(answer); // تابع فرضی برای بررسی پاسخ روی سرور
setStatus('success');
} catch (err: any) {
setStatus('typing');
setError(err);
}
}
return (
<>
<h2>مسابقه شهرشناسی</h2>
<p>در کدام شهر بیلبوردی وجود دارد که هوا را به آب آشامیدنی تبدیل میکند؟</p>
<form onSubmit={handleSubmit}>
<textarea
value={answer}
onChange={(e) => setAnswer(e.target.value)}
disabled={status === 'submitting'}
/>
<br />
<button disabled={answer.length === 0 || status === 'submitting'}>
ثبت پاسخ
</button>
{error && <p className="Error">{error.message}</p>}
</form>
</>
);
}
// یک امولیتور فرضی برای شبکه
function submitForm(answer: string): Promise<void> {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (answer.toLowerCase().trim() === 'lima' || answer.trim() === 'لیما') {
resolve();
} else {
reject(new Error('حدس خوبی بود اما اشتباه است. دوباره تلاش کنید!'));
}
}, 1500);
});
}
برنامهنویسی اظهارگرایانه (Declarative): یعنی توصیف لایههای مختلف UI برای هر وضعیت بصری، به جای دستکاری تکتک المانهای DOM (دستوری).
هنگام توسعه کامپوننت، ابتدا تمام وضعیتهای ظاهری معتبر آن را لیست کنید.
عوامل انسانی و کامپیوتری که باعث جابجایی بین این وضعیتها میشوند را مشخص کنید.
با کمک useState استیتها را به حداقل رسانده و از ایجاد وضعیتهای غیرممکن (تناقضها) جلوگیری کنید.
در نهایت Event Handlerها را متصل کنید تا با تغییر استیت، ریآکت به طور خودکار صفحه را بازسازی کند.
این محتوا کاملا رایگان توسط تیم کدلپر ترجمه شده و در اختیار شما کاربران عزیز قرار گرفته است، هر گونه کپی برداری برای مقاصد غیر رایگان و بدون ذکر منبع، مورد پیگیری قانونی قرار میگیرد.
ترجمه شده از منبع: https://react.dev/learn