React Portals (порталы) — это специальный механизм в React, который позволяет рендерить дочерние элементы в DOM-узел, находящийся вне иерархии родительского компонента, сохраняя при этом все свойства React (контекст, пропсы, события).
"Рендери здесь, но отображай там"
ReactDOM.createPortal(child, container)
Где:
child
— React-элемент (JSX)container
— DOM-элемент, куда будет вставлен элементОбычно добавляется прямо перед закрывающим тегом </body>
:
// В public/index.html
<div id="modal-root"></div>
import ReactDOM from 'react-dom';
function Modal({ children }) {
// Создаём ссылку на DOM-элемент
const modalRoot = document.getElementById('modal-root');
// Если элемента нет (для SSR), возвращаем null
if (!modalRoot) return null;
return ReactDOM.createPortal(
<div className="modal">
{children}
</div>,
modalRoot
);
}
function App() {
const [isOpen, setIsOpen] = useState(false);
return (
<div>
<button onClick={() => setIsOpen(true)}>
Открыть модалку
</button>
{isOpen && (
<Modal>
<h2>Заголовок модалки</h2>
<p>Содержимое модального окна</p>
<button onClick={() => setIsOpen(false)}>
Закрыть
</button>
</Modal>
)}
</div>
);
}
Порталы сохраняют React-контекст, даже если рендерятся вне родительского дерева.
События из портала всплывают по React-дереву, а не по DOM-дереву. Это значит, что событие клика внутри портала будет поймано родителем в React-компоненте.
Компоненты в портале полностью управляются React, включая эффекты и очистку.
function Modal({ children, onClose }) {
const modalRoot = document.getElementById('modal-root');
return ReactDOM.createPortal(
<>
<div className="modal-overlay" onClick={onClose} />
<div className="modal-content">
{children}
</div>
</>,
modalRoot
);
}
function DropdownMenu({ anchorEl, children }) {
const menuRoot = document.getElementById('dropdown-root');
const rect = anchorEl.getBoundingClientRect();
return ReactDOM.createPortal(
<div
className="dropdown-menu"
style={{
position: 'absolute',
top: rect.bottom,
left: rect.left
}}
>
{children}
</div>,
menuRoot
);
}
position: fixed
для простых случаевpublic/index.html
)ReactDOM
createPortal(child, container)
Пример полной реализации модального окна:
import { useEffect } from 'react';
import ReactDOM from 'react-dom';
function Modal({ children, onClose }) {
const modalRoot = document.getElementById('modal-root');
useEffect(() => {
const handleEscape = (e) => {
if (e.key === 'Escape') onClose();
};
document.body.style.overflow = 'hidden';
document.addEventListener('keydown', handleEscape);
return () => {
document.body.style.overflow = '';
document.removeEventListener('keydown', handleEscape);
};
}, [onClose]);
if (!modalRoot) return null;
return ReactDOM.createPortal(
<div
className="modal"
role="dialog"
aria-modal="true"
>
<div className="modal-content">
{children}
</div>
</div>,
modalRoot
);
}