Что такое Portals в React?

MiddleReact · Frontend·Обновлено 8 июля 2026
Коротко
React Portals — механизм рендеринга дочерних компонентов в DOM-узел, находящийся вне иерархии родительского компонента. При этом компонент остаётся частью React-дерева и сохраняет все возможности: контекст, события, состояние.

React Portals

React Portals позволяют рендерить JSX в произвольный DOM-узел, который находится вне DOM-контейнера родительского компонента. Несмотря на это, портал остаётся полноценной частью React-дерева компонентов.

Синтаксис

Portal создаётся с помощью функции ReactDOM.createPortal(children, domNode), которая принимает два аргумента:

  • children — любой рендеримый React-элемент
  • domNode — DOM-узел, в который будет помещён контент

Зачем нужны Portals

Главная проблема, которую решают порталы — ограничения CSS-стекинга. Когда модальное окно или тултип находится внутри элемента с overflow: hidden, transform, z-index или position, эти стили влияют на дочерние элементы. Портал позволяет вырваться из этого CSS-контекста и рендерить, например, модалку прямо в document.body.

Типичные сценарии использования:

  • Модальные окна и диалоги
  • Тултипы и поповеры
  • Нотификации и тосты
  • Дропдауны, выходящие за границы контейнера

Особенности поведения

Всплытие событий работает по React-дереву, а не по DOM-дереву. Событие клика внутри портала всплывёт к родителю в React-дереве, даже если в DOM они находятся в разных ветках. Это часто удивляет разработчиков.

Контекст полностью сохраняется. Компонент внутри портала имеет доступ ко всем Provider-ам, которые находятся выше него в дереве React.

Жизненный цикл также работает стандартно — useEffect, useState, рефы и всё остальное функционируют как обычно.

React 18+ и createPortal вне рендера

Начиная с React 18 createPortal можно использовать для гидратации серверного HTML внутри порталов, что улучшает поддержку SSR-сценариев.

Важные нюансы

  • DOM-узел должен существовать до монтирования портала
  • При анмаунте компонента контент портала также удаляется автоматически
  • Не стоит злоупотреблять порталами — они усложняют отладку и нарушают интуитивную связь DOM и React-дерева

Что хочет услышать интервьюер

Понимание проблемы, которую решают порталы: вырваться из CSS-контекста родителя (overflow, z-index, transform)

Знание API: ReactDOM.createPortal(children, domNode) и где его вызывать

Понимание того, что портал остаётся в React-дереве — контекст и всплытие событий работают по React-иерархии

Знание типичных сценариев: модалки, тултипы, нотификации, дропдауны

Понимание, что DOM-узел назначения должен существовать до монтирования портала

Пример: Модальное окно через Portal

import { useEffect, useRef } from 'react';
import ReactDOM from 'react-dom';

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

export function Modal({ isOpen, onClose, children }: ModalProps) {
  // Создаём DOM-узел один раз и храним в рефе
  const elRef = useRef<HTMLDivElement | null>(null);

  if (!elRef.current) {
    elRef.current = document.createElement('div');
  }

  useEffect(() => {
    const modalRoot = document.getElementById('modal-root');
    if (!modalRoot || !elRef.current) return;

    // Прикрепляем узел при монтировании
    modalRoot.appendChild(elRef.current);

    return () => {
      // Убираем узел при размонтировании
      modalRoot.removeChild(elRef.current!);
    };
  }, []);

  if (!isOpen) return null;

  // Рендерим содержимое в отдельный DOM-узел,
  // но компонент остаётся частью React-дерева
  return ReactDOM.createPortal(
    <div className="modal-overlay" onClick={onClose}>
      <div className="modal-content" onClick={(e) => e.stopPropagation()}>
        {children}
      </div>
    </div>,
    elRef.current
  );
}

Пример: Демонстрация всплытия событий через React-дерево

import ReactDOM from 'react-dom';

function Parent() {
  const handleClick = () => {
    // Этот обработчик сработает при клике внутри портала,
    // потому что всплытие идёт по React-дереву, а не DOM
    console.log('Клик поймал родитель через React-дерево');
  };

  const portalTarget = document.getElementById('portal-root')!;

  return (
    <div onClick={handleClick}>
      <p>Родительский компонент</p>
      {ReactDOM.createPortal(
        // Кнопка находится в #portal-root в DOM,
        // но React считает её дочерней для Parent
        <button>Кнопка внутри портала</button>,
        portalTarget
      )}
    </div>
  );
}

Типичные ошибки

Думают, что события внутри портала не всплывают к React-родителю — на самом деле всплытие идёт по React-дереву, а не DOM

Забывают, что DOM-узел для портала нужно создать заранее (например, через useEffect или статически в HTML)

Путают портал с ленивой загрузкой или Suspense — это разные концепции

Не понимают, зачем нужен портал, если можно просто поднять компонент выше — не объясняют проблему CSS-стекинга

Создают новый DOM-узел внутри рендера каждый раз вместо того, чтобы хранить его в рефе или модуле

Лучшие курсы по теме

изображение курса

TypeScript с нуля

Антон Ларичев
AI-тренажерыAI-тренажеры
Практика в студииПрактика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.8
3 999 ₽ 6 990 ₽
Подробнее
изображение курса

Feature-Sliced Design

Антон Ларичев
AI-тренажерыAI-тренажеры
Практика в студииПрактика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.5
3 999 ₽ 6 990 ₽
Подробнее
изображение курса

Next.js - с нуля

Антон Ларичев
AI-тренажерыAI-тренажеры
Практика в студииПрактика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.7
3 999 ₽ 6 990 ₽
Подробнее