فهرست سرفصل‌های React
خانه (Home) مقدمه (Intro) شروع کار (Get Started) اولین اپ (First App) رندر HTML (Render HTML) ارتقا (Upgrade) آشنایی با ES6 (ES6) کلاس ها در ES6 (ES6 Classes) توابع پیکانی ES6 (ES6 Arrow Functions) متغیرها در ES6 (ES6 Variables) متد map روی آرایه ها (ES6 Array map()) دیس ترکچرینگ (تقسیم ساختاری) (ES6 Destructuring) عملگر پخش (Spread) (ES6 Spread Operator) ماژول ها در ES6 (ES6 Modules) عملگر سه تایی (Ternary) (ES6 Ternary Operator) رشته های قالبی (Template Strings) (ES6 Template Strings) مقدمه JSX (JSX Intro) عبارت ها در JSX (JSX Expressions) خصیصه ها در JSX (JSX Attributes) شرط ها در JSX (JSX If Statements) کامپوننت ها (Components) کلاس ها (Class) پراپس ها (Props) (Props) دیس ترکچرینگ پراپس ها (Props Destructuring) پراپس children (Props Children) رویدادها (Events) رندر شرطی (Conditionals) لیست ها (Lists) فرم ها (Forms) ارسال فرم (Forms Submit) فیلد Textarea (Textarea) فیلد Select (Select) چند ورودی در فرم (Multiple Inputs) چک باکس (Checkbox) دکمه های رادیویی (Radio) پورتال ها (Portals) Suspense (Suspense) استایل دهی با CSS (CSS Styling) CSS Modules (CSS Modules) CSS-in-JS (CSS-in-JS) مسیریابی (Router) (Router) ترنزیشن ها (Transitions) (Transitions) Forward Ref (Forward Ref) کامپوننت های مرتبه بالاتر (HOC) (HOC) استایل دهی با Sass (Sass) هوکس چیست؟ (What is Hooks?) هوک useState (useState) هوک useEffect (useEffect) هوک useContext (useContext) هوک useRef (useRef) هوک useReducer (useReducer) هوک useCallback (useCallback) هوک useMemo (useMemo) هوک های سفارشی (Custom Hooks) کامپایلر (Compiler) کوئیز (آزمون کوتاه) (Quiz) تمرین ها (Exercises) سیلابس (سرفصل دوره) (Syllabus) برنامه مطالعه (Study Plan) سرور (Server) آمادگی مصاحبه (Interview Prep) گواهینامه (Certificate)
نتیجه‌ای برای جستجو یافت نشد.
React

React — پورتال ها (Portals)

آخرین بروزرسانی: 1404/08/24

پورتال ها (Portals)

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

پورتال های React چیست؟

به صورت عادی، خروجی هر کامپوننت بچه همان کامپوننت در DOM است. یعنی div داخل همان بخش صفحه قرار می گیرد. اما پورتال های React اجازه می دهند HTML را در جای دیگری از DOM رندر کنیم.

تابع createPortal از پکیج react-dom این کار را انجام می دهد. این کار برای مودال ها، تولتیپ ها و منوهای شناور خیلی کاربردی است؛ چون باید از محدودیت های والد فرار کنند.

مثال ساده بدون استفاده از پورتال

اول یک مثال معمولی داریم. در این حالت خروجی کامپوننت دقیقاً زیر خودش در DOM رندر می شود.

function myChild() {
  return (
    <div>
      Welcome
    </div>
  );
}

مشاهده در ادیتور

در این حالت div حاوی متن Welcome بچه مستقیم myChild در DOM است. یعنی هر استایل یا محدودیت والد روی این div هم تأثیر می گذارد.

همان مثال با استفاده از پورتال

حالا همان خروجی را با createPortal رندر می کنیم. این بار div مستقیماً داخل document.body قرار می گیرد.

import { createPortal } from 'react-dom';

function myChild() {
  return createPortal(
    <div>
      Welcome
    </div>,
    document.body
  );
}

مشاهده در ادیتور

اینجا کامپوننت هنوز همان myChild است. اما محتوای آن از درخت DOM والد جدا شده و به بدنه اصلی صفحه وصل شده است. این یعنی استایل هایی مثل overflow والد دیگر محتوای ما را محدود نمی کنند.

سینتکس تابع createPortal در پورتال های React

برای ساخت پورتال های React باید از تابع createPortal استفاده کنیم. این تابع در پکیج react-dom قرار دارد.

import { createPortal } from 'react-dom';

createPortal(children, domNode);

مشاهده در ادیتور

آرگومان children می تواند هر محتوای React باشد؛ مثل المنت ها یا رشته ها. آرگومان domNode یک المنت واقعی DOM است که می خواهیم خروجی در آن جا رندر شود؛ مثلاً document.body یا یک div مخصوص مودال.

ساخت مودال با پورتال ها در React

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

در این مثال، کامپوننت Modal محتوایش را با createPortal داخل document.body رندر می کند. در عین حال، کنترل باز و بسته شدن مودال در MyApp و state آن مدیریت می شود.

import { createRoot } from 'react-dom/client';
import { useState } from 'react';
import { createPortal } from 'react-dom';

function Modal({ isOpen, onClose, children }) {
  if (!isOpen) {
    return null;
  }

  return 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'
      }}
    >
      <div
        style={{
          background: 'white',
          padding: '20px',
          borderRadius: '8px'
        }}
      >
        {children}
        <button onClick={onClose}>
          Close
        </button>
      </div>
    </div>,
    document.body
  );
}

function MyApp() {
  const [isOpen, setIsOpen] = useState(false);

  return (
    <div>
      <h1>
        My App
      </h1>
      <button
        onClick={() => {
          setIsOpen(true);
        }}
      >
        Open Modal
      </button>

      <Modal
        isOpen={isOpen}
        onClose={() => {
          setIsOpen(false);
        }}
      >
        <h2>
          Modal Content
        </h2>
        <p>
          This content is rendered outside the App component!
        </p>
      </Modal>
    </div>
  );
}

createRoot(document.getElementById('root')).render(
  <MyApp />
);

مشاهده در ادیتور

در اینجا state isOpen تعیین می کند مودال باز باشد یا بسته. وقتی دکمه Open Modal را می زنی، isOpen برابر true می شود و Modal محتوا را از طریق createPortal روی کل صفحه نشان می دهد.

چرا از پورتال های React استفاده می کنیم؟

گاهی والد روی خودش overflow: hidden یا z-index خاص دارد. در این حالت، منوها یا مودال های داخل آن بریده می شوند یا زیر عناصر دیگر می روند.

با پورتال ها می توانیم این اجزا را در document.body رندر کنیم. اما همچنان کامپوننت ها و state در جای اصلی خودشان در درخت React می مانند.

  • ساخت مودال ها و دیالوگ ها بدون مشکل لایه بندی.
  • تولتیپ ها و منوهای شناور روی کل صفحه.
  • نوتیفیکیشن ها و پیام های گوشه صفحه.

حبابی شدن رویدادها در پورتال ها

حبابی شدن رویداد (Event Bubbling) یعنی رویداد از بچه به والدها بالا می رود. نکته مهم این است که در پورتال های React، رویدادها طبق درخت React حبابی می شوند، نه طبق جای واقعی در DOM.

یعنی حتی اگر دکمه در document.body رندر شود، هنوز می تواند رویداد onClick والد خود در درخت React را فعال کند.

مثال دکمه شناور با پورتال

در این مثال، یک دکمه شناور با PortalButton داریم. این دکمه بیرون از div اصلی در DOM رندر می شود؛ اما کلیک آن هنوز روی div والد در React هم اثر می گذارد.

import { createRoot } from 'react-dom/client';
import { useState } from 'react';
import { createPortal } from 'react-dom';

function PortalButton({ onClick, children }) {
  return createPortal(
    <button
      onClick={onClick}
      style={{
        position: 'fixed',
        bottom: '20px',
        right: '20px',
        padding: '10px',
        background: 'blue',
        color: 'white'
      }}
    >
      {children}
    </button>,
    document.body
  );
}

function App() {
  const [count1, setCount1] = useState(0);
  const [count2, setCount2] = useState(0);

  return (
    <div
      style={{
        padding: '20px',
        border: '2px solid black',
        margin: '20px'
      }}
      onClick={() => {
        setCount1(c => c + 1);
      }}
    >
      <h2>
        Div Clicked: {count1}
      </h2>
      <h2>
        Button Clicked: {count2}
      </h2>
      <p>
        The floating button is rendered outside this box using a portal,
        but its clicks still bubble up to this parent div!
      </p>
      <p>
        Try to click the div element as well, to see the count increase
      </p>

      <PortalButton
        onClick={() => {
          setCount2(c => c + 1);
        }}
      >
        Floating Button
      </PortalButton>
    </div>
  );
}

createRoot(document.getElementById('root')).render(
  <App />
);

مشاهده در ادیتور

وقتی روی دکمه کلیک می کنی، اول شمارنده دکمه زیاد می شود. سپس رویداد روی div هم حبابی می شود و شمارنده Div Clicked هم افزایش پیدا می کند.

گام به گام ساخت یک پورتال ساده

برای تمرین، یک پورتال ساده در پروژه خودت بساز. این مراحل را به ترتیب انجام بده.

  1. در فایل بالا، createPortal را از react-dom ایمپورت کن.
  2. یک کامپوننت جدید مثل PortalBox بساز.
  3. در PortalBox، از createPortal استفاده کن و یک div ساده رندر کن.
  4. به عنوان domNode از document.body استفاده کن.
  5. در کامپوننت اصلی، PortalBox را صدا بزن و نتیجه را در صفحه ببین.

نکته: اگر در بخش فرم ها مودال تأیید داری، می توانی آن را با پورتال های React تمیزتر و بدون مشکل overflow بسازی.

جمع بندی سریع پورتال ها

  • پورتال های React محتوا را بیرون از DOM والد رندر می کنند.
  • تابع createPortal(children, domNode) هسته ساخت پورتال است.
  • برای مودال ها، تولتیپ ها و نوتیفیکیشن ها عالی عمل می کنند.
  • رویدادها طبق درخت React حبابی می شوند، نه محل واقعی DOM.
  • برای مرور، صفحه پورتال های React را ذخیره کن.