فارسی

با پورتال‌های ری‌اکت، مودال‌ها، تولتیپ‌ها و نوتیفیکیشن‌ها را خارج از درخت کامپوننت رندر کنید و سیستم رویداد و کانتکست را حفظ نمایید. یک راهنمای ضروری برای توسعه‌دهندگان جهانی.

تسلط بر پورتال‌های ری‌اکت: رندر کامپوننت‌ها فراتر از سلسله‌مراتب DOM

در چشم‌انداز گسترده توسعه وب مدرن، ری‌اکت به توسعه‌دهندگان بی‌شماری در سراسر جهان این قدرت را داده است تا رابط‌های کاربری پویا و بسیار تعاملی بسازند. معماری مبتنی بر کامپوننت آن، ساختارهای پیچیده UI را ساده می‌کند و قابلیت استفاده مجدد و نگهداری را ارتقا می‌دهد. با این حال، حتی با طراحی زیبای ری‌اکت، توسعه‌دهندگان گاهی با سناریوهایی روبرو می‌شوند که رویکرد استاندارد رندر کامپوننت – جایی که کامپوننت‌ها خروجی خود را به عنوان فرزندان در عنصر DOM والد خود رندر می‌کنند – محدودیت‌های قابل توجهی را ایجاد می‌کند.

یک دیالوگ مودال را در نظر بگیرید که باید بالاتر از تمام محتوای دیگر ظاهر شود، یک بنر نوتیفیکیشن که به صورت سراسری شناور است، یا یک منوی زمینه که باید از مرزهای یک کانتینر والد با سرریز (overflow) فرار کند. در این شرایط، رویکرد متداول رندر کامپوننت‌ها مستقیماً در سلسله‌مراتب DOM والدشان می‌تواند به چالش‌هایی در استایل‌دهی (مانند تداخلات z-index)، مشکلات چیدمان و پیچیدگی‌های انتشار رویداد منجر شود. این دقیقاً جایی است که پورتال‌های ری‌اکت (React Portals) به عنوان یک ابزار قدرتمند و ضروری در زرادخانه یک توسعه‌دهنده ری‌اکت وارد عمل می‌شوند.

این راهنمای جامع به عمق الگوی پورتال ری‌اکت می‌پردازد و مفاهیم بنیادی، کاربردهای عملی، ملاحظات پیشرفته و بهترین شیوه‌های آن را بررسی می‌کند. چه یک توسعه‌دهنده باتجربه ری‌اکت باشید یا تازه سفر خود را آغاز کرده باشید، درک پورتال‌ها امکانات جدیدی را برای ساختن تجربیات کاربری واقعاً قوی و قابل دسترس در سطح جهانی برای شما باز خواهد کرد.

درک چالش اصلی: محدودیت‌های سلسله‌مراتب DOM

کامپوننت‌های ری‌اکت، به طور پیش‌فرض، خروجی خود را در گره DOM کامپوننت والد خود رندر می‌کنند. این یک نگاشت مستقیم بین درخت کامپوننت ری‌اکت و درخت DOM مرورگر ایجاد می‌کند. در حالی که این رابطه شهودی و به طور کلی مفید است، زمانی که نمایش بصری یک کامپوننت نیاز به رهایی از محدودیت‌های والد خود داشته باشد، می‌تواند به یک مانع تبدیل شود.

سناریوهای رایج و نقاط دردناک آن‌ها:

در هر یک از این سناریوها، تلاش برای دستیابی به نتیجه بصری مطلوب با استفاده از رندر استاندارد ری‌اکت، اغلب منجر به CSS پیچیده، مقادیر بیش از حد `z-index` یا منطق موقعیت‌یابی پیچیده‌ای می‌شود که نگهداری و مقیاس‌پذیری آن دشوار است. اینجاست که پورتال‌های ری‌اکت یک راه‌حل تمیز و اصولی ارائه می‌دهند.

پورتال ری‌اکت دقیقاً چیست؟

یک پورتال ری‌اکت یک روش درجه یک برای رندر کردن فرزندان در یک گره DOM فراهم می‌کند که خارج از سلسله‌مراتب DOM کامپوننت والد وجود دارد. با وجود رندر شدن در یک عنصر DOM فیزیکی متفاوت، محتوای پورتال همچنان طوری رفتار می‌کند که گویی یک فرزند مستقیم در درخت کامپوننت ری‌اکت است. این بدان معناست که همان کانتکست ری‌اکت (مثلاً مقادیر Context API) را حفظ می‌کند و در سیستم حباب رویداد (event bubbling) ری‌اکت شرکت می‌کند.

هسته پورتال‌های ری‌اکت در متد `ReactDOM.createPortal()` نهفته است. امضای آن ساده است:

ReactDOM.createPortal(child, container)

هنگامی که از `ReactDOM.createPortal()` استفاده می‌کنید، ری‌اکت یک زیردرخت DOM مجازی جدید تحت گره DOM `container` مشخص شده ایجاد می‌کند. با این حال، این زیردرخت جدید هنوز به طور منطقی به کامپوننتی که پورتال را ایجاد کرده است متصل است. این «اتصال منطقی» کلید درک این است که چرا حباب رویداد و کانتکست همانطور که انتظار می‌رود کار می‌کنند.

راه‌اندازی اولین پورتال ری‌اکت: یک مثال ساده از مودال

بیایید یک مورد استفاده رایج را بررسی کنیم: ایجاد یک دیالوگ مودال. برای پیاده‌سازی یک پورتال، ابتدا به یک عنصر DOM هدف در `index.html` خود (یا هر جایی که فایل HTML ریشه اپلیکیشن شما قرار دارد) نیاز دارید که محتوای پورتال در آن رندر شود.

مرحله ۱: آماده‌سازی گره DOM هدف

فایل `public/index.html` خود (یا معادل آن) را باز کرده و یک عنصر `div` جدید اضافه کنید. رسم بر این است که این عنصر درست قبل از تگ پایانی `body`، خارج از ریشه اصلی اپلیکیشن ری‌اکت شما اضافه شود.


<body>
  <!-- ریشه اصلی اپلیکیشن ری‌اکت شما -->
  <div id="root"></div>

  <!-- اینجا جایی است که محتوای پورتال ما رندر خواهد شد -->
  <div id="modal-root"></div>
</body>

مرحله ۲: ایجاد کامپوننت پورتال

حالا، بیایید یک کامپوننت مودال ساده بسازیم که از یک پورتال استفاده می‌کند.


// Modal.js
import React, { useEffect, useRef } from 'react';
import ReactDOM from 'react-dom';

const modalRoot = document.getElementById('modal-root');

const Modal = ({ children, isOpen, onClose }) => {
  const el = useRef(document.createElement('div'));

  useEffect(() => {
    // اضافه کردن div به ریشه مودال هنگام mount شدن کامپوننت
    modalRoot.appendChild(el.current);

    // پاک‌سازی: حذف div هنگام unmount شدن کامپوننت
    return () => {
      modalRoot.removeChild(el.current);
    };
  }, []); // آرایه وابستگی خالی به این معناست که این کد یک بار در mount و یک بار در unmount اجرا می‌شود

  if (!isOpen) {
    return null; // اگر مودال باز نیست، چیزی رندر نکن
  }

  return ReactDOM.createPortal(
    <div style={{
      position: 'fixed',
      top: 0,
      left: 0,
      right: 0,
      bottom: 0,
      backgroundColor: 'rgba(0, 0, 0, 0.5)',
      display: 'flex',
      alignItems: 'center',
      justifyContent: 'center',
      zIndex: 1000 // اطمینان از قرار گرفتن در بالا
    }}>
      <div style={{
        backgroundColor: 'white',
        padding: '20px',
        borderRadius: '8px',
        boxShadow: '0 4px 8px rgba(0, 0, 0, 0.2)',
        maxWidth: '500px',
        width: '90%'
      }}>
        {children}
        <button onClick={onClose} style={{ marginTop: '15px' }}>بستن مودال</button>
      </div>
    </div>,
    el.current // رندر محتوای مودال در div ایجاد شده ما، که داخل modalRoot است
  );
};

export default Modal;

در این مثال، ما برای هر نمونه مودال یک عنصر `div` جدید (`el.current`) ایجاد کرده و آن را به `modal-root` اضافه می‌کنیم. این به ما امکان می‌دهد در صورت نیاز چندین مودال را مدیریت کنیم بدون اینکه در چرخه حیات یا محتوای یکدیگر تداخل ایجاد کنند. محتوای واقعی مودال (پوشش و جعبه سفید) سپس با استفاده از `ReactDOM.createPortal` در این `el.current` رندر می‌شود.

مرحله ۳: استفاده از کامپوننت مودال


// App.js
import React, { useState } from 'react';
import Modal from './Modal'; // با فرض اینکه Modal.js در همان دایرکتوری است

function App() {
  const [isModalOpen, setIsModalOpen] = useState(false);

  const handleOpenModal = () => setIsModalOpen(true);
  const handleCloseModal = () => setIsModalOpen(false);

  return (
    <div style={{ padding: '20px' }}>
      <h1>مثال پورتال ری‌اکت</h1>
      <p>این محتوا بخشی از درخت اصلی اپلیکیشن است.</p>
      <button onClick={handleOpenModal}>باز کردن مودال سراسری</button>

      <Modal isOpen={isModalOpen} onClose={handleCloseModal}>
        <h3>درود از پورتال!</h3>
        <p>این محتوای مودال خارج از div با شناسه 'root' رندر شده است، اما همچنان توسط ری‌اکت مدیریت می‌شود.</p>
      </Modal>
    </div>
  );
}

export default App;

با وجود اینکه کامپوننت `Modal` در داخل کامپوننت `App` رندر می‌شود (که خود در داخل div با شناسه `root` قرار دارد)، خروجی DOM واقعی آن در داخل div با شناسه `modal-root` ظاهر می‌شود. این تضمین می‌کند که مودال بدون مشکلات `z-index` یا `overflow` روی همه چیز قرار می‌گیرد، در حالی که همچنان از مدیریت state و چرخه حیات کامپوننت ری‌اکت بهره‌مند است.

کاربردهای کلیدی و کاربردهای پیشرفته پورتال‌های ری‌اکت

در حالی که مودال‌ها یک مثال برجسته هستند، کاربرد پورتال‌های ری‌اکت بسیار فراتر از پاپ‌آپ‌های ساده است. بیایید سناریوهای پیشرفته‌تری را بررسی کنیم که در آن‌ها پورتال‌ها راه‌حل‌های زیبایی ارائه می‌دهند.

۱. سیستم‌های مودال و دیالوگ قوی

همانطور که دیدیم، پورتال‌ها پیاده‌سازی مودال را ساده می‌کنند. مزایای کلیدی عبارتند از:

۲. تولتیپ‌ها، پاپ‌اورها و دراپ‌داون‌های پویا

مشابه مودال‌ها، این عناصر اغلب نیاز دارند که در مجاورت یک عنصر فعال‌کننده ظاهر شوند اما از چیدمان‌های والد محدود خود نیز خارج شوند.

۳. نوتیفیکیشن‌های سراسری و پیام‌های Toast

اپلیکیشن‌ها اغلب به سیستمی برای نمایش پیام‌های غیر مسدودکننده و موقتی نیاز دارند (مثلاً «آیتم به سبد خرید اضافه شد!»، «اتصال شبکه قطع شد»).

۴. منوهای زمینه سفارشی

هنگامی که یک کاربر روی یک عنصر کلیک-راست می‌کند، اغلب یک منوی زمینه ظاهر می‌شود. این منو باید دقیقاً در مکان نشانگر ماوس قرار گیرد و روی تمام محتوای دیگر قرار بگیرد. پورتال‌ها در اینجا ایده‌آل هستند:

۵. ادغام با کتابخانه‌های شخص ثالث یا عناصر DOM غیر ری‌اکتی

تصور کنید یک اپلیکیشن موجود دارید که بخشی از UI آن توسط یک کتابخانه جاوا اسکریپت قدیمی یا شاید یک راه‌حل نقشه‌برداری سفارشی که از گره‌های DOM خود استفاده می‌کند، مدیریت می‌شود. اگر می‌خواهید یک کامپوننت ری‌اکت کوچک و تعاملی را در چنین گره DOM خارجی رندر کنید، `ReactDOM.createPortal` پل شماست.

ملاحظات پیشرفته هنگام استفاده از پورتال‌های ری‌اکت

در حالی که پورتال‌ها مشکلات پیچیده رندر را حل می‌کنند، درک چگونگی تعامل آن‌ها با سایر ویژگی‌های ری‌اکت و DOM برای استفاده مؤثر از آن‌ها و جلوگیری از دام‌های رایج، حیاتی است.

۱. حباب رویداد (Event Bubbling): یک تمایز حیاتی

یکی از قدرتمندترین و اغلب اشتباه درک شده‌ترین جنبه‌های پورتال‌های ری‌اکت، رفتار آن‌ها در مورد حباب رویداد است. با وجود اینکه در یک گره DOM کاملاً متفاوت رندر می‌شوند، رویدادهایی که از عناصر داخل یک پورتال شلیک می‌شوند، همچنان در درخت کامپوننت ری‌اکت به سمت بالا حباب می‌کنند، گویی که هیچ پورتالی وجود ندارد. این به این دلیل است که سیستم رویداد ری‌اکت مصنوعی است و در اکثر موارد مستقل از حباب رویداد DOM بومی کار می‌کند.

۲. Context API و پورتال‌ها

Context API مکانیزم ری‌اکت برای به اشتراک گذاشتن مقادیر (مانند تم‌ها، وضعیت احراز هویت کاربر) در سراسر درخت کامپوننت بدون prop-drilling است. خوشبختانه، Context به طور یکپارچه با پورتال‌ها کار می‌کند.

۳. دسترسی‌پذیری (A11y) با پورتال‌ها

ساختن UIهای قابل دسترس برای مخاطبان جهانی بسیار مهم است و پورتال‌ها ملاحظات A11y خاصی را به خصوص برای مودال‌ها و دیالوگ‌ها معرفی می‌کنند.

۴. چالش‌ها و راه‌حل‌های استایل‌دهی

در حالی که پورتال‌ها مشکلات سلسله‌مراتب DOM را حل می‌کنند، آن‌ها به طور جادویی تمام پیچیدگی‌های استایل‌دهی را حل نمی‌کنند.

۵. ملاحظات رندر سمت سرور (SSR)

اگر اپلیکیشن شما از رندر سمت سرور (SSR) استفاده می‌کند، باید مراقب رفتار پورتال‌ها باشید.

۶. تست کامپوننت‌هایی که از پورتال‌ها استفاده می‌کنند

تست کامپوننت‌هایی که از طریق پورتال‌ها رندر می‌شوند می‌تواند کمی متفاوت باشد اما توسط کتابخانه‌های تست محبوب مانند React Testing Library به خوبی پشتیبانی می‌شود.

اشتباهات رایج و بهترین شیوه‌ها برای پورتال‌های ری‌اکت

برای اطمینان از اینکه استفاده شما از پورتال‌های ری‌اکت مؤثر، قابل نگهداری و با عملکرد خوب است، این بهترین شیوه‌ها را در نظر بگیرید و از اشتباهات رایج اجتناب کنید:

۱. از پورتال‌ها بیش از حد استفاده نکنید

پورتال‌ها قدرتمند هستند، اما باید با احتیاط استفاده شوند. اگر خروجی بصری یک کامپوننت را می‌توان بدون شکستن سلسله‌مراتب DOM به دست آورد (مثلاً با استفاده از موقعیت‌یابی نسبی یا مطلق در یک والد بدون سرریز)، این کار را انجام دهید. اتکای بیش از حد به پورتال‌ها گاهی اوقات می‌تواند اشکال‌زدایی ساختار DOM را در صورت عدم مدیریت دقیق، پیچیده کند.

۲. از پاک‌سازی مناسب (Unmounting) اطمینان حاصل کنید

اگر به صورت پویا یک گره DOM برای پورتال خود ایجاد می‌کنید (مانند مثال `Modal` ما با `el.current`)، اطمینان حاصل کنید که آن را هنگام unmount شدن کامپوننتی که از پورتال استفاده می‌کند، پاک‌سازی می‌کنید. تابع پاک‌سازی `useEffect` برای این کار عالی است و از نشت حافظه و شلوغ شدن DOM با عناصر یتیم جلوگیری می‌کند.


useEffect(() => {
  // ... اضافه کردن el.current
  return () => {
    // ... حذف el.current;
  };
}, []);

اگر همیشه در یک گره DOM ثابت و از پیش موجود (مانند یک `modal-root` واحد) رندر می‌کنید، پاک‌سازی خود *گره* لازم نیست، اما اطمینان از اینکه *محتوای پورتال* به درستی هنگام unmount شدن کامپوننت والد unmount می‌شود، همچنان به طور خودکار توسط ری‌اکت انجام می‌شود.

۳. ملاحظات عملکرد

برای اکثر موارد استفاده (مودال‌ها، تولتیپ‌ها)، پورتال‌ها تأثیر عملکردی ناچیزی دارند. با این حال، اگر در حال رندر کردن یک کامپوننت بسیار بزرگ یا با به‌روزرسانی مکرر از طریق یک پورتال هستید، بهینه‌سازی‌های عملکردی معمول ری‌اکت (مانند `React.memo`، `useCallback`، `useMemo`) را مانند هر کامپوننت پیچیده دیگری در نظر بگیرید.

۴. همیشه دسترسی‌پذیری را در اولویت قرار دهید

همانطور که تأکید شد، دسترسی‌پذیری حیاتی است. اطمینان حاصل کنید که محتوای رندر شده با پورتال شما از دستورالعمل‌های ARIA پیروی می‌کند و تجربه روانی را برای همه کاربران، به ویژه آن‌هایی که به ناوبری با کیبورد یا صفحه‌خوان‌ها متکی هستند، فراهم می‌کند.

۵. از HTML معنایی در داخل پورتال‌ها استفاده کنید

در حالی که پورتال به شما امکان می‌دهد محتوا را از نظر بصری در هر جایی رندر کنید، به یاد داشته باشید که از عناصر HTML معنایی در داخل فرزندان پورتال خود استفاده کنید. به عنوان مثال، یک دیالوگ باید از عنصر `

` (در صورت پشتیبانی و استایل‌دهی) یا یک `div` با `role="dialog"` و ویژگی‌های ARIA مناسب استفاده کند. این به دسترسی‌پذیری و SEO کمک می‌کند.

۶. منطق پورتال خود را زمینه‌سازی کنید

برای اپلیکیشن‌های پیچیده، در نظر بگیرید که منطق پورتال خود را در یک کامپوننت قابل استفاده مجدد یا یک هوک سفارشی کپسوله کنید. به عنوان مثال، یک هوک `useModal` یا یک کامپوننت عمومی `PortalWrapper` می‌تواند فراخوانی `ReactDOM.createPortal` را انتزاعی کرده و ایجاد/پاک‌سازی گره DOM را مدیریت کند، و کد اپلیکیشن شما را تمیزتر و ماژولارتر کند.


// مثالی از یک PortalWrapper ساده
import React, { useEffect, useState } from 'react';
import ReactDOM from 'react-dom';

const createWrapperAndAppendToBody = (wrapperId) => {
  const wrapperElement = document.createElement('div');
  wrapperElement.setAttribute('id', wrapperId);
  document.body.appendChild(wrapperElement);
  return wrapperElement;
};

const PortalWrapper = ({ children, wrapperId = 'portal-wrapper' }) => {
  const [wrapperElement, setWrapperElement] = useState(null);

  useEffect(() => {
    let element = document.getElementById(wrapperId);
    let systemCreated = false;
    // اگر عنصری با شناسه wrapperId وجود نداشته باشد، آن را ایجاد کرده و به body اضافه کنید
    if (!element) {
      systemCreated = true;
      element = createWrapperAndAppendToBody(wrapperId);
    }
    setWrapperElement(element);

    return () => {
      // عنصری که به صورت برنامه‌ریزی شده ایجاد شده را حذف کنید
      if (systemCreated && element.parentNode) {
        element.parentNode.removeChild(element);
      }
    };
  }, [wrapperId]);

  if (!wrapperElement) return null;

  return ReactDOM.createPortal(children, wrapperElement);
};

export default PortalWrapper;

این `PortalWrapper` به شما امکان می‌دهد به سادگی هر محتوایی را بپوشانید، و آن در یک گره DOM ایجاد شده (و پاک‌سازی شده) به صورت پویا با شناسه مشخص شده رندر خواهد شد، که استفاده از آن را در سراسر اپلیکیشن شما ساده می‌کند.

نتیجه‌گیری: توانمندسازی توسعه UI جهانی با پورتال‌های ری‌اکت

پورتال‌های ری‌اکت یک ویژگی زیبا و ضروری هستند که به توسعه‌دهندگان این امکان را می‌دهند تا از محدودیت‌های سنتی سلسله‌مراتب DOM رها شوند. آن‌ها یک مکانیزم قوی برای ساخت عناصر UI پیچیده و تعاملی مانند مودال‌ها، تولتیپ‌ها، نوتیفیکیشن‌ها و منوهای زمینه فراهم می‌کنند و اطمینان می‌دهند که هم از نظر بصری و هم از نظر عملکردی به درستی رفتار می‌کنند.

با درک اینکه چگونه پورتال‌ها درخت کامپوننت منطقی ری‌اکت را حفظ می‌کنند و جریان یکپارچه حباب رویداد و کانتکست را امکان‌پذیر می‌سازند، توسعه‌دهندگان می‌توانند رابط‌های کاربری واقعاً پیچیده و قابل دسترسی ایجاد کنند که به مخاطبان متنوع جهانی پاسخ می‌دهد. چه در حال ساخت یک وب‌سایت ساده باشید یا یک اپلیکیشن سازمانی پیچیده، تسلط بر پورتال‌های ری‌اکت به طور قابل توجهی توانایی شما را در ساخت تجربیات کاربری انعطاف‌پذیر، با کارایی بالا و لذت‌بخش افزایش خواهد داد. این الگوی قدرتمند را بپذیرید و سطح بعدی توسعه ری‌اکت را باز کنید!

تسلط بر پورتال‌های ری‌اکت: رندر کامپوننت‌ها فراتر از سلسله‌مراتب DOM | MLOG