Portals
Portals fornece uma forma elegante de renderizar um elemento filho dentro de um nó DOM que existe fora da hierarquia do componente pai.
ReactDOM.createPortal(child, container)
O primeiro argumento (child
) é qualquer elemento filho React renderizável, como um elemento, string ou fragmento. O segundo argumento (container
) é um elemento DOM.
Utilização
Normalmente, quando retornamos um elemento pelo método render de um componente ele é montado dentro do DOM como um filho do nó pai mais próximo:
render() {
// React monta uma nova div e renderiza o filho dentro dela
return (
<div> {this.props.children}
</div> );
}
Entretanto, em algumas situações é útil inserir um elemento filho em um local diferente no DOM:
render() {
// React *não* cria uma nova div. Ele renderiza os filhos dentro do `domNode`.
// `domNode` é qualquer nó DOM válido, independente da sua localização no DOM.
return ReactDOM.createPortal(
this.props.children,
domNode );
}
Um caso típico do uso de portals é quando um componente pai tem o estilo overflow: hidden
ou z-index
, mas você precisa que o filho visualmente “saia” desse contêiner. Por exemplo, caixas de diálogo, hovercards e tooltips.
Nota:
Quando estiver trabalhando com portals, lembre-se que tratar o evento focus se torna muito importante.
No caso dos modals, assegure-se que todos possam interagir com eles seguindo as práticas descritas em WAI-ARIA Modal Authoring Practices.
Propagação de Eventos Através do Portals
Apesar de um portal poder estar em qualquer lugar na árvore DOM, seu comportamento é como o de qualquer outro elemento React filho. Funcionalidades como contexto funcionam da mesma forma independente se o filho é um portal, pois o portal ainda existe na árvore React independentemente da posição que esteja na árvore DOM.
Isso inclui a propagação de eventos. Um evento disparado dentro de um portal será propagado para os elementos antecessores da árvore React, mesmo que estes não sejam antecessores na árvore DOM. Considere a seguinte estrutura HTML:
<html>
<body>
<div id="app-root"></div>
<div id="modal-root"></div>
</body>
</html>
Um componente Pai
em #app-root
será capaz de capturar a propagação de um evento não tratado vindo do nó irmão #modal-root
.
// Estes dois contêineres são irmãos no 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() {
// O elemento portal é inserido na árvore DOM depois que
// os componentes filhos de `Modal` são montados, o que significa que os filhos
// serão montados em um nó DOM separado. Se um componente
// filho precisa ser colocado na árvore DOM
// imediatamente quando é montado, por exemplo para medir um
// nó DOM ou usar 'autoFocus' em um descendente, adicione
// state ao Modal e renderize o filho apenas quando o Modal
// estiver inserido na árvore 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() { // Isso é disparado quando o botão no filho é clicado, // atualizando o state do componente Pai, mesmo que o filho // não seja um descendente direto no DOM. this.setState(state => ({ clicks: state.clicks + 1 })); }
render() {
return (
<div onClick={this.handleClick}> <p>Número de cliques: {this.state.clicks}</p>
<p>
Abra o DevTools do navegador
para observar que o botão
não é um filho da div
com o onClick.
</p>
<Modal> <Child /> </Modal> </div>
);
}
}
function Child() {
// O evento de clique nesse botão irá propagar para o ascendente, // porque o atributo 'onClick' não está definido return (
<div className="modal">
<button>Clicar</button> </div>
);
}
ReactDOM.render(<Parent />, appRoot);
Capturar um evento propagado a partir de um portal em um componente pai permite o desenvolvimento de abstrações mais flexíveis que não dependem diretamente de portals. Por exemplo, se você renderizar um componente <Modal />
, o componente pai pode capturar seus eventos independentemente se são implementados usando portals.