کامپوننتها برای ایجاد تعامل با کاربر، نیاز دارند تغییرات روی صفحه را مدیریت کنند. تایپ کردن درون یک فرم باید مقدار اینپوت را بهروز کند، کلیک روی دکمه «بعدی» در اسلایدر باید تصویر را تغییر دهد و کلیک روی دکمه «خرید» باید محصول را به سبد اضافه کند.
کامپوننتها به ابزاری برای «به خاطر سپردن» اشیاء نیاز دارند: مقدار فعلی اینپوت، تصویر فعلی یا سبد خرید. در React، به این حافظه اختصاصی و داخلی کامپوننت، State (وضعیت) میگویند.
چیزهایی که در این درس یاد میگیرید:
-
چرا متغیرهای معمولی جاوااسکریپت برای بهروزرسانی صفحه کافی نیستند؟
-
نحوه اضافه کردن متغیر وضعیت با هوک
useState -
بررسی خروجیهای جفتِ هوک
useState -
دلیل محلی (Local) و خصوصی (Private) بودن State
چرا یک متغیر معمولی کافی نیست؟
فرض کنید یک کامپوننت اسلایدر عکس داریم. میخواهیم با کلیک روی دکمه "Next"، متغیر index یک واحد زیاد شود تا عکس بعدی را نشان دهیم. کدی شبیه به این مینویسیم:
// کدی که کار نمیکند!
import { sculptureList } from './data.js';
export default function Gallery() {
let index = 0; // یک متغیر معمولی
function handleClick() {
index = index + 1; // مقدار متغیر تغییر میکند اما...
}
let sculpture = sculptureList[index];
return (
<>
<button onClick={handleClick}>Next</button>
<h2>{sculpture.name}</h2>
<img src={sculpture.url} alt={sculpture.alt} />
</>
);
}
اگر این کد را اجرا کنید و روی دکمه کلیک کنید، هیچ اتفاقی روی صفحه نمیافتد! تابع handleClick متغیر را آپدیت میکند اما دو مشکل بزرگ وجود دارد:
-
متغیرهای محلی در بین رندرهای React دوام نمیآورند: وقتی React برای بار دوم این کامپوننت را رندر میکند، همهچیز را از صفر میسازد. یعنی خط
let index = 0دوباره اجرا شده و تغییرات قبلی پاک میشوند. -
تغییر متغیرهای معمولی باعث رندر مجدد نمیشود: React اصلاً متوجه نمیشود که متغیر تغییر کرده و باید ظاهر صفحه را با دیتای جدید بازسازی (Re-render) کند.
🛠 راه حل چیست؟
برای اینکه دیتای جدید روی ظاهر صفحه اثر بگذارد، به دو ابزار نیاز داریم:
-
ذخیره و حفظ دیتا در بین رندرها.
-
تحریک کردن React برای رندر مجدد کامپوننت با دیتای جدید.
هوک useState دقیقاً این دو کار را برای ما انجام میدهد.
اضافه کردن State به کامپوننت
برای استفاده از این قابلیت، ابتدا useState را در بالاترین خط فایل از کتابخانه react ایمپورت کنید:
import { useState } from 'react';
سپس خط let index = 0; را با ساختار زیر جایگزین کنید:
const [index, setIndex] = useState(0);
در این ساختار، index متغیر وضعیت ماست و setIndex تابع اصلاحکننده (Setter Function) آن است. سینتکس [...] نیز Array Destructuring نام دارد و به ما اجازه میدهد دو عضو آرایه خروجی useState را مستقیماً تفکیک کنیم.
حالا تابع کلیک را به این صورت اصلاح میکنیم:
// کدی که کاملاً کار میکند
import { useState } from 'react';
import { sculptureList } from './data.js';
export default function Gallery() {
const [index, setIndex] = useState(0); // تعریف استیت با مقدار اولیه 0
function handleClick() {
setIndex(index + 1; // آپدیت استیت و دستور رندر مجدد به React
}
let sculpture = sculptureList[index];
return (
<>
<button onClick={handleClick}>Next</button>
<h2><i>{sculpture.name} </i></h2>
<img src={sculpture.url} alt={sculpture.alt} />
</>
);
}
هوک (Hook) چیست؟
در React، به تابع useState و هر تابع دیگری که نام آن با کلمه use شروع میشود، هوک (Hook) میگویند. هوکها توابع ویژهای هستند که فقط در زمان رندر شدن کامپوننتهای React در دسترس هستند و به شما اجازه میدهند به قابلیتهای مختلف این فریمورک (مثل استیت، افکتها و...) متصل شوید.
⚠️ یک تله مهم (قانون هوکها)
هوکها را فقط و فقط باید در بالاترین سطح کامپوننت خود (Top Level) صدا بزنید. شما اجازه ندارید هوکها را داخل ساختارهای شرطی (if)، حلقهها (for) یا توابع تودرتو بنویسید. به هوکها به چشم اعلامیههای بیقیدوشرط نیازهای کامپوننت نگاه کنید؛ همانطور که importها را بالا مینویسید، هوکها نیز باید در بالاترین بخش بدنه کامپوننت چیده شوند.
آناتومی و رفتار داخلی useState
وقتی useState(0) را صدا میزنید، به React میگویید: «من میخواهم این کامپوننت متغیری را با مقدار اولیه 0 به خاطر بسپارد».
در جریان تعامل کاربر، سناریوی زیر رخ میدهد:
-
رندر اول: شما مقدار
0را به عنوان مقدار اولیه پاس دادهاید. React جفت[0, setIndex]را برمیگرداند. -
تغییر استیت: کاربر روی دکمه کلیک میکند و تابع
setIndex(index + 1)اجرا میشود. از آنجا کهindexصفر است، دکمه مقدار1را به تابع پاس میدهد. React به خاطر میسپارد که وضعیت این کامپوننت اکنون1است و رندر جدیدی را کلید میزند. -
رندر دوم: React کدهای کامپوننت را دوباره از بالا میخواند. با رسیدن به خط
useState(0)، مقدار اولیه را نادیده میگیرد؛ چون به خاطر دارد که شما آن را به1تغییر داده بودید. پس این بار جفت[1, setIndex]را تحویل میدهد.
استفاده از چند متغیر وضعیت (Multiple States)
شما میتوانید به هر تعداد که نیاز دارید، متغیرهای وضعیت متفاوتی را در یک کامپوننت تعریف کنید. در مثال زیر، دو استیت مستقل از هم داریم؛ یکی برای ذخیره شماره عکس (index) و دیگری یک مقدار بانیولین برای باز و بسته کردن بخش توضیحات (showMore):
import { useState } from 'react';
import { sculptureList } from './data.js';
export default function Gallery() {
const [index, setIndex] = useState(0);
const [showMore, setShowMore] = useState(false); // استیت دوم
function handleNextClick() {
setIndex(index + 1);
}
function handleMoreClick() {
setShowMore(!showMore); // معکوس کردن مقدار بوسیله اپراتور نقیض
}
let sculpture = sculptureList[index];
return (
<>
<button onClick={handleNextClick}>Next</button>
<h2>{sculpture.name}</h2>
<button onClick={handleMoreClick}>
{showMore ? 'پنهان کردن' : 'نمایش'} جزئیات
</button>
{showMore && <p>{sculpture.description}</p>}
<img src={sculpture.url} alt={sculpture.alt} />
</>
);
}
💡 نکته معماری: اگر استیتها مانند مثال بالا هیچ ارتباط منطقی با هم ندارند، تفکیک آنها کار درستی است. اما اگر متوجه شدید که دو یا چند متغیر وضعیت همیشه با هم و در یک لحظه تغییر میکنند (مثل فیلدهای یک فرم بزرگ)، بهتر است آنها را ترکیب کرده و در قالب یک شیء (Object) درون یک استیت واحد مدیریت کنید.
🔎 عمیقتر شوید: React از کجا میداند کدام استیت را برگرداند؟
شاید بپرسید: تابع useState هیچ شناسه یا آیدی (Identifier) ورودی دریافت نمیکند، پس React از کجا میفهمد که دکمه اول مربوط به index است و دکمه دوم مربوط به showMore؟ آیا کدهای ما را پارس میکند؟ خیر!
راز این موضوع در ترتیب ثابت فراخوانی هوکها (Stable Call Order) است. اگر قانون بالا را رعایت کنید و هوکها را در شرطها نپیچید، آنها همیشه با یک ترتیب ثابت در هر رندر اجرا میشوند.
React در پشت صحنه، برای هر کامپوننت یک آرایه از جفتهای استیت نگهداری میکند و یک شمارنده (Index) دارد که قبل از رندر صفر میشود. با هر بار فراخوانی useState، جفتِ بعدی را از آرایه برداشته و شمارنده را یک واحد جلو میبرد.
استیتها کاملاً مستقل و خصوصی (Isolated & Private) هستند
استیتها کاملاً به نمونه فیزیکی کامپوننت روی صفحه متصل هستند. به این معنی که اگر شما از یک کامپوننت <Gallery /> دو بار در یک صفحه استفاده کنید، هر کدام از آنها یک حافظه کاملاً مستقل و ایزوله خواهند داشت:
import Gallery from './Gallery.js';
export default function Page() {
return (
<div className="Page">
{/* این دو گالری هیچ تأثیری روی استیت یکدیگر ندارند */}
<Gallery />
<Gallery />
</div>
);
}
تغییر استیت در گالری اول، هیچ تغییری در گالری دوم ایجاد نمیکند. متغیر استیت برعکس متغیرهای معمولی که بالای ماژول تعریف میشوند، به یک تابع یا خط کد وصل نیست، بلکه جایش روی صفحه مشخص است.
همچنین، کامپوننت والد (Page) هیچ دسترسی یا اطلاعی از استیت داخلی <Gallery /> ندارد. استیت کاملاً خصوصی (Private) است. این ویژگی به شما اجازه میدهد بدون نگرانی از خراب شدن بقیه بخشهای برنامه، به هر کامپوننتی استیت اضافه یا از آن حذف کنید.
خلاصه نکات کلیدی (Recap)
-
هر زمان کامپوننت نیاز دارد اطلاعاتی را بین دو رندر به خاطر بسپارد، از State استفاده کنید.
-
متغیرهای وضعیت با فراخوانی هوک
useStateتعریف میشوند. -
هوکها توابع خاصی هستند که با
useشروع میشوند و باید به صورت مشروطنشده و در بالاترین سطح کامپوننت قرار بگیرند. -
هوک
useStateیک جفت مقدار پس میدهد: مقدار فعلی استیت و تابع بهروزرسانی آن. -
استیتها کاملاً خصوصی و منحصر به همان کامپوننت روی صفحه هستند و نمونههای متعددی از یک کامپوننت، استیتهای مجزا دارند.
این محتوا کاملا رایگان توسط تیم کدلپر ترجمه شده و در اختیار شما کاربران عزیز قرار گرفته است، هر گونه کپی برداری برای مقاصد غیر رایگان و بدون ذکر منبع، مورد پیگیری قانونی قرار میگیرد.
ترجمه شده از منبع: منبع مستندات