← Назад к вопросам

Что такое порталы?

2.0 Middle🔥 121 комментариев
#React

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI26 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

React Порталы (Portals)

Портал — это специальный механизм React, который позволяет отрендерить компонент в другом месте DOM дерева, вне его обычной иерархии. Это полезно для модальных окон, выпадающих меню и тултипов.

Что такое портал

Обычно React компонент рендерится как дочерний элемент своего родителя в DOM. Портал позволяет рендерить компонент в произвольный узел DOM, которому не является родителем компонента.

Базовое использование

import { createPortal } from react;

const Modal = ({ isOpen, children }) => {
  if (!isOpen) return null;
  
  // Рендерим содержимое в элемент с id="modal-root"
  return createPortal(
    <div className="modal-overlay">
      <div className="modal-content">
        {children}
      </div>
    </div>,
    document.getElementById(modal-root)
  );
};

В HTML необходимо добавить целевой контейнер:

<html>
  <body>
    <div id="root"></div>
    <div id="modal-root"></div>  <!-- Портал рендерится сюда -->
  </body>
</html>

Практический пример: модальное окно

interface ModalProps {
  isOpen: boolean;
  onClose: () => void;
  title: string;
  children: React.ReactNode;
}

const Modal = ({ isOpen, onClose, title, children }: ModalProps) => {
  if (!isOpen) return null;
  
  return createPortal(
    <div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50">
      <div className="bg-white rounded-lg shadow-lg p-6 w-96">
        <div className="flex justify-between items-center mb-4">
          <h2 className="text-xl font-bold">{title}</h2>
          <button
            onClick={onClose}
            className="text-gray-400 hover:text-gray-600"
          ></button>
        </div>
        <div>{children}</div>
        <button
          onClick={onClose}
          className="mt-4 px-4 py-2 bg-blue-500 text-white rounded"
        >
          Закрыть
        </button>
      </div>
    </div>,
    document.body
  );
};

// Использование
const App = () => {
  const [isOpen, setIsOpen] = useState(false);
  
  return (
    <div>
      <button onClick={() => setIsOpen(true)}>Открыть модаль</button>
      <Modal isOpen={isOpen} onClose={() => setIsOpen(false)} title="Привет">
        <p>Содержимое модали</p>
      </Modal>
    </div>
  );
};

Портал для выпадающего меню

const Dropdown = ({ trigger, items }: Props) => {
  const [isOpen, setIsOpen] = useState(false);
  const ref = useRef<HTMLDivElement>(null);
  
  // Получи позицию триггера
  const rect = ref.current?.getBoundingClientRect();
  
  return (
    <div ref={ref} className="relative inline-block">
      <button onClick={() => setIsOpen(!isOpen)}>{trigger}</button>
      
      {isOpen && createPortal(
        <ul
          className="absolute bg-white border rounded shadow-lg z-10"
          style={{
            top: (rect?.bottom || 0) + "px",
            left: (rect?.left || 0) + "px",
            width: (rect?.width || 0) + "px"
          }}
        >
          {items.map(item => (
            <li key={item.id} className="px-4 py-2 hover:bg-gray-100">
              {item.label}
            </li>
          ))}
        </ul>,
        document.body
      )}
    </div>
  );
};

Когда использовать порталы

1. Модальные окна

  • Всегда рендерятся в корне, не под другими элементами
  • Проще управлять z-index

2. Тултипы и Popover

  • Нужно выйти за границы контейнера
  • Позиционирование относительно документа

3. Выпадающие меню

  • Избежать overflow: hidden на родителях
  • Всегда видно сверху остальных элементов

4. Уведомления (Toast/Notifications)

  • Центральный список уведомлений
  • Легко управлять слоем UI

Важные моменты

Event Bubbling через портал

События в портале всё ещё всплывают в дерево компонентов (не в DOM):

const Parent = () => {
  const handleClick = (e: React.MouseEvent) => {
    console.log("Parent clicked");
  };
  
  return (
    <div onClick={handleClick}>
      {createPortal(
        <button onClick={(e) => console.log("Button clicked")}>
          Клик
        </button>,
        document.body
      )}
    </div>
  );
};

// При клике на кнопку выведет:
// Button clicked
// Parent clicked (всплывает в дерево React!)

Захват фокуса (Aria Dialog)

Для доступности в модали нужно управлять фокусом:

const AccessibleModal = ({ isOpen, onClose }: Props) => {
  const modalRef = useRef<HTMLDivElement>(null);
  
  useEffect(() => {
    if (isOpen) {
      modalRef.current?.focus();
    }
  }, [isOpen]);
  
  return createPortal(
    <div
      ref={modalRef}
      role="dialog"
      aria-modal="true"
      tabIndex={-1}
    >
      {/* содержимое */}
    </div>,
    document.body
  );
};

Альтернативы

  • CSS position: fixed — проще для простых случаев
  • CSS position: absolute с position: relative родителем
  • Shadow DOM — для полной изоляции стилей (редко)

Резюме

Порталы — мощный инструмент для управления слоями UI. Используй их для модалей, тултипов и выпадающих меню, когда стандартное DOM-дерево не подходит.