در برنامهنویسی با ریآکت، بسیار پیش میآید که بخواهید وضعیت (State) دو یا چند کامپوننت همیشه همگام با یکدیگر تغییر کند. برای این کار، باید استیت را از درون کامپوننتهای فرزند حذف کرده، آن را به نزدیکترین پدر مشترک (Closest Common Parent) منتقل کنید و سپس دیتای مورد نیاز را از طریق پروپها (props) به فرزندان پاس دهید. این فرآیند اصطلاحاً بالا بردن استیت (Lifting State Up) نامیده میشود و یکی از رایجترین الگوها در معماری کامپوننتهای ریآکت است.
نحوه هماهنگسازی چند کامپوننت با بالا بردن استیت به کامپوننت پدر
تفاوت بین کامپوننتهای کنترلشده (Controlled) و کنترلنشده (Uncontrolled)
مفهوم «منبع واحد حقیقت» (Single Source of Truth) در مدیریت دادهها
تصور کنید یک کامپوننت پدر به نام Accordion داریم که دو کامپوننت فرزند به نام Panel را رندر میکند. در حالت عادی، هر پنل یک استیت محلی به نام isActive دارد که باز یا بسته بودن خودش را کنترل میکند:
در این ساختار، پنلها کاملاً مستقل هستند؛ یعنی باز کردن پنل اول هیچ تأثیری روی پنل دوم نمیگذارد. اما اگر بخواهیم طبق قوانین UI آکاردئون، در هر لحظه فقط و فقط یک پنل باز باشد و با باز شدن پنل دوم، پنل اول خودبهخود بسته شود چه؟
برای اعمال این هماهنگی، باید طی ۳ گام استیت را به کامپوننت پدر منتقل کنیم:
ابتدا باید کنترلِ متغیر isActive را از خود پنل سلب کنیم. خط تعریف استیت را از کامپوننت Panel پاک کرده و isActive را به لیست ورودیهای پروپ آن اضافه میکنیم:
// قبل از تغییر: مستقل و کنترلنشده
// const [isActive, setIsActive] = useState(false);
// بعد از تغییر: وابسته به پروپ پدر
function Panel({ title, children, isActive }) { ... }
پدر مشترک این دو پنل، کامپوننت Accordion است. برای تست اولیه، پروپ isActive را به صورت هاردکد (Hardcoded) به پنلها پاس میدهیم:
export default function Accordion() {
return (
<>
<Panel title="درباره ما" isActive={true}>محتوا...</Panel>
<Panel title="تماس با ما" isActive={false}>محتوا...</Panel>
</>
);
}
حالا برای اینکه مدیریت پویا داشته باشیم، ماهیت استیت را در پدر تغییر میدهیم. به جای استفاده از دو وضعیت بولین مجزا، یک استیت عددی به نام activeIndex تعریف میکنیم که ایندکسِ پنل فعال را در خود نگه میدارد.
چون فرزندان به صورت مستقیم دسترسی به آپدیت استیت پدر ندارند، یک تابع دسترسی (Event Handler) به نام onShow را نیز به عنوان پروپ به فرزند پاس میدهیم:
import { useState } from 'react';
export default function Accordion() {
// ۰ یعنی پنل اول باز است، ۱ یعنی پنل دوم و...
const [activeIndex, setActiveIndex] = useState(0);
return (
<>
<h2>آشنایی با شهر آلماتی</h2>
<Panel
title="درباره شهر"
isActive={activeIndex === 0}
onShow={() => setActiveIndex(0)}
>
آلماتی بزرگترین شهر قزاقستان است و جمعیتی حدود ۲ میلیون نفر دارد.
</Panel>
<Panel
title="ریشهشناسی نام"
isActive={activeIndex === 1}
onShow={() => setActiveIndex(1)}
>
نام این شهر از کلمه قزاقی "الما" به معنی سیب گرفته شده است.
</Panel>
</>
);
}
interface PanelProps {
title: string;
children: React.ReactNode;
isActive: boolean;
onShow: () => void;
}
function Panel({ title, children, isActive, onShow }: PanelProps) {
return (
<section className="panel">
<h3>{title}</h3>
{isActive ? (
<p>{children}</p>
) : (
<button onClick={onShow}>نمایش</button>
)}
</section>
);
}
با این کار، بالا بردن استیت کامل میشود! حالا مدیریت پنلها کاملاً متمرکز است و با کلیک روی دکمه هر پنل، استیت ریشه تغییر کرده و لایوت به صورت خودکار و بدون تداخل بازسازی میشود.
در فرهنگ لغات ریآکت، توسعهدهندگان معمولاً کامپوننتها را بر اساس نحوه مدیریت دیتایشان به دو دسته تقسیم میکنند:
کامپوننت کنترلنشده (Uncontrolled): کامپوننتی است که یک استیت داخلی و محلی دارد. کامپوننت پدر به سختی میتواند روی رفتار آن تأثیر بگذارد (مانند نسخه اولیه Panel). استفاده از این کامپوننتها ساده است چون نیاز به کانفیگ زیادی ندارند، اما برای هماهنگسازی با دیگر اجزا انعطافپذیری کمی دارند.
کامپوننت کنترلشده (Controlled): کامپوننتی است که اطلاعات حیاتی آن نه از استیت داخلی، بلکه کاملاً از طریق پروپهای ارسالی پدر هدایت میشود (مانند نسخه نهایی Panel). این ساختار حداکثر انعطافپذیری را به شما میدهد و به کامپوننت پدر اجازه میدهد تا رفتار فرزند را کاملاً دیکته کند.
| ویژگی | کامپوننت کنترلنشده (Uncontrolled) | کامپوننت کنترلشده (Controlled) |
| منبع ذخیره داده | استیت داخلی (useState) | پروپهای دریافتی از پدر (props) |
| میزان کانفیگ اولیه | بسیار کم و راحت | نیازمند تنظیم پروپ و Handler در پدر |
| قابلیت هماهنگسازی | ضعیف و مستقل | عالی و منعطف برای کارهای گروهی |
در یک برنامه ریآکتی، هر کامپوننت میتواند استیت مخصوص به خود را داشته باشد. برخی از استیتها در پایینترین سطح درخت کامپوننتها (مثل مقدار در حال تایپ در یک input) و برخی دیگر در بالاترین لایههای برنامه (مثل روتر یا استیت احراز هویت) ذخیره میشوند.
اصل منبع واحد حقیقت به این معناست که برای هر داده یا وضعیت منحصربهفرد در برنامه، باید فقط یک کامپوننت مشخص مالک و نگهدارنده اصلی آن باشد. به جای کپی کردن و ساخت استیتهای همنام و تکراری در کامپوننتهای مختلف، دیتای مشترک را بالا ببرید تا مطمئن شوید همه فرزندان از یک منبع یکسان و بهروز تغذیه میشوند.
برای هماهنگ کردن دو کامپوننت، استیت محلی آنها را حذف کرده و به پدر مشترکشان منتقل کنید.
دیتای وضعیت را از پدر به صورت پروپ به فرزندان پاس دهید.
توابع تغییر استیت (Event Handlers) را به فرزندان بسپارید تا بتوانند تغییر استیت را به گوش پدر برسانند.
تفکیک کامپوننتها به دو نوع کنترلشده و کنترلنشده به شما در طراحی ماژولار و ساختاریافته کمک شایانی میکند.
این محتوا کاملا رایگان توسط تیم کدلپر ترجمه شده و در اختیار شما کاربران عزیز قرار گرفته است، هر گونه کپی برداری برای مقاصد غیر رایگان و بدون ذکر منبع، مورد پیگیری قانونی قرار میگیرد.
ترجمه شده از منبع: https://react.dev/learn