3.11 Порталы
Доступны с 16 версии.
Порталы предоставляют первоклассный способ отображения дочерних элементов в узел DOM, который существует вне иерархии DOM родительского компонента.
ReactDOM.createPortal(child, container)
Первым аргументом (child
) является любой отображаемый потомок React,
такой как элемент, строка или фрагмент. Второй аргумент (container
)
является элементом DOM.
Как правило, когда вы возвращаете элемент из метода отрисовки компонента, он монтируется в DOM как дочерний элемент ближайшего родительского узла:
render() {
// React монтирует новый div и отрисовывает в него потомок
return (
<div>
{this.props.children}
</div>
);
}
Однако иногда полезно вставлять дочерний элемент в другое место в DOM:
render() {
// React не создаёт новый div. Он отрисовывает потомок в `domNode`.
// `domNode` - это всегда валидный DOM-узел, независимо от его места в DOM.
return ReactDOM.createPortal(
this.props.children,
domNode,
);
}
Типичный вариант использования порталов - это когда родительский компонент
имеет overflow: hidden
или z-index
стиль, но вам нужно, чтобы дочерний компонент
визуально «выходил» из своего контейнера. Например, диалоги, всплывающие подсказки.
Замечание!
Важно помнить, что при работе с порталами вам необходимо следить за тем, что вы следуете рекомендациям по общедоступности.Несмотря на то, что портал может быть где угодно в дереве DOM, он ведет себя как обычный дочерний элемент React во всех отношениях. Такие функции, как контекст, работают как и ранее, независимо от того, является ли дочерний элемент порталом, поскольку портал все еще существует в дереве React независимо от его положения в дереве DOM.
Это же касается и всплытия события. Событие, созданное внутри портала, будет распространяться к предкам в объемлющем дереве React, даже если они не являются предками в дереве DOM. Представим следующую структуру HTML:
<html>
<body>
<div id="app-root"></div>
<div id="modal-root"></div>
</body>
</html>
Компонент Parent
в #app-root
мог бы поймать неперехваченное всплывающее
событие из соседнего узла #modal-root
.
// Эти два контейнера являются соседями в DOM
const appRoot = document.getElementById('app-root');
const modalRoot = document.getElementById('modal-root');
class Modal extends React.Component {
constructor(props) {
super(props);
this.el = document.createElement('div');
}
componentDidMount() {
/*
Элемент портала вставлен в дерево DOM после того, как потомки Modal
были монтированы, что означает, что потомки будут монтированы в отдельный
узел DOM.
Если дочерний компонент требует присоединения к дереву DOM сразу после
его монтирования, например, для измерения узла DOM или использования
«autoFocus» в потомке, добавьте состояние в Modal и отрисуйте дочерние
элементы, после того, как Modal будет вставлен в DOM дерево.
*/
modalRoot.appendChild(this.el);
}
componentWillUnmount() {
modalRoot.removeChild(this.el);
}
render() {
return ReactDOM.createPortal(
this.props.children,
this.el,
);
}
}
class Parent extends React.Component {
constructor(props) {
super(props);
this.state = {clicks: 0};
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
// Он сработает, когда кнопка в Child будет нажата,
// обновляя состояние Parent, даже если кнопка
// не является его прямым потомком в DOM.
this.setState(prevState => ({
clicks: prevState.clicks + 1
}));
}
render() {
return (
<div onClick={this.handleClick}>
<p>Число кликов: {this.state.clicks}</p>
<p>
Откройте DevTools браузера,
чтобы увидеть, что кнопка button
не является потомком div
с обработчиком onClick.
</p>
<Modal>
<Child />
</Modal>
</div>
);
}
}
function Child() {
// Событие клика на этой кнопке будет всплывать к Parent,
// так как нет заданного 'onClick' атрибута
return (
<div className="modal">
<button>Click</button>
</div>
);
}
ReactDOM.render(<Parent />, appRoot);
Захват события, всплывающего из портала в родительском
компоненте, позволяет создавать более гибкие абстракции, которые по своей сути не зависят от
порталов. Например, если вы отрисовываете компонент <Modal />
, родитель может
захватывать свои события независимо от того, реализован ли он с помощью порталов.