Что такое Portals в 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-узел внутри рендера каждый раз вместо того, чтобы хранить его в рефе или модуле


