کامپوننتها برای ایجاد تعامل با کاربر، نیاز دارند تغییرات روی صفحه را مدیریت کنند. تایپ کردن درون یک فرم باید مقدار اینپوت را بهروز کند، کلیک روی دکمه «بعدی» در اسلایدر باید تصویر را تغییر دهد و کلیک روی دکمه «خرید» باید محصول را به سبد اضافه کند.
کامپوننتها به ابزاری برای «به خاطر سپردن» اشیاء نیاز دارند: مقدار فعلی اینپوت، تصویر فعلی یا سبد خرید. در 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 دقیقاً این دو کار را برای ما انجام میدهد.
برای استفاده از این قابلیت، ابتدا 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} />
</>
);
}
در 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] را تحویل میدهد.
شما میتوانید به هر تعداد که نیاز دارید، متغیرهای وضعیت متفاوتی را در یک کامپوننت تعریف کنید. در مثال زیر، دو استیت مستقل از هم داریم؛ یکی برای ذخیره شماره عکس (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) درون یک استیت واحد مدیریت کنید.
شاید بپرسید: تابع useState هیچ شناسه یا آیدی (Identifier) ورودی دریافت نمیکند، پس React از کجا میفهمد که دکمه اول مربوط به index است و دکمه دوم مربوط به showMore؟ آیا کدهای ما را پارس میکند؟ خیر!
راز این موضوع در ترتیب ثابت فراخوانی هوکها (Stable Call Order) است. اگر قانون بالا را رعایت کنید و هوکها را در شرطها نپیچید، آنها همیشه با یک ترتیب ثابت در هر رندر اجرا میشوند.
React در پشت صحنه، برای هر کامپوننت یک آرایه از جفتهای استیت نگهداری میکند و یک شمارنده (Index) دارد که قبل از رندر صفر میشود. با هر بار فراخوانی useState، جفتِ بعدی را از آرایه برداشته و شمارنده را یک واحد جلو میبرد.
استیتها کاملاً به نمونه فیزیکی کامپوننت روی صفحه متصل هستند. به این معنی که اگر شما از یک کامپوننت <Gallery /> دو بار در یک صفحه استفاده کنید، هر کدام از آنها یک حافظه کاملاً مستقل و ایزوله خواهند داشت:
import Gallery from './Gallery.js';
export default function Page() {
return (
<div className="Page">
{/* این دو گالری هیچ تأثیری روی استیت یکدیگر ندارند */}
<Gallery />
<Gallery />
</div>
);
}
تغییر استیت در گالری اول، هیچ تغییری در گالری دوم ایجاد نمیکند. متغیر استیت برعکس متغیرهای معمولی که بالای ماژول تعریف میشوند، به یک تابع یا خط کد وصل نیست، بلکه جایش روی صفحه مشخص است.
همچنین، کامپوننت والد (Page) هیچ دسترسی یا اطلاعی از استیت داخلی <Gallery /> ندارد. استیت کاملاً خصوصی (Private) است. این ویژگی به شما اجازه میدهد بدون نگرانی از خراب شدن بقیه بخشهای برنامه، به هر کامپوننتی استیت اضافه یا از آن حذف کنید.
هر زمان کامپوننت نیاز دارد اطلاعاتی را بین دو رندر به خاطر بسپارد، از State استفاده کنید.
متغیرهای وضعیت با فراخوانی هوک useState تعریف میشوند.
هوکها توابع خاصی هستند که با use شروع میشوند و باید به صورت مشروطنشده و در بالاترین سطح کامپوننت قرار بگیرند.
هوک useState یک جفت مقدار پس میدهد: مقدار فعلی استیت و تابع بهروزرسانی آن.
استیتها کاملاً خصوصی و منحصر به همان کامپوننت روی صفحه هستند و نمونههای متعددی از یک کامپوننت، استیتهای مجزا دارند.
این محتوا کاملا رایگان توسط تیم کدلپر ترجمه شده و در اختیار شما کاربران عزیز قرار گرفته است، هر گونه کپی برداری برای مقاصد غیر رایگان و بدون ذکر منبع، مورد پیگیری قانونی قرار میگیرد.
ترجمه شده از منبع: https://react.dev/learn