此文章是翻译Portals这篇React(版本v16.2.0)官方文档。
Portals
Portal 提供最好的方式去渲染子节点到父组件DOM 层级之外的DOM 节点上。
ReactDOM.createPortal(child, container)
第一个参数(child)是任何能够渲染的React 子节点,像元素、字符串、或者fragment。第二个参数(container)是一个DOM 元素。
Usage
通常讲,当你从组件的render 方法返回一个元素,该元素仅能加载到离其最近的父节点的子元素的DOM:
render() {// React mounts a new and renders the children into itreturn (<div>{this.props.children}</div>)}
然而,有时候将其插入到子节点到DOM 的不同位置也是有用的:
render() {// React does *not* create a new div. It renders the children into `domNode`.// `domNode` is any valid DOM node, regardless of its location in the DOM.return ReactDOM.createPortal(this.props.children,domNode,);}
对于portal 的一个典型用例是当父组件有overflow: hidden 或z-index 样式,但你需要子组件能够在视觉上“跳出(break out)”其容器。例如,对话框、hovercards以及提示框。
注意:
记住这点非常重要,当在使用portals时,你需要确保遵循合适的可访问指南。
Event Bubbling Through Portals
即使portal 可以在DOM 树中的任何位置,它的行为都像普通的React 子节点。无论子节点是否为portal,类似于context 的功能都是完全相同的,因为无论DOM 树中的位置如何,portal 仍然存在于React 树中。
这包括事件冒泡。从portal 内部触发的事件将传播到包含React 树中的祖先,即使这些元素不是DOM 树中的祖先。假设以下HTML 结构:
<html><body><div id="app-root"></div><div id="modal-root"></div></body></html>
在#app-root 里的Parent 组件能够捕获到一个未被捕获的,从兄弟节点#modal-root 冒泡上来的事件。
// These two containers are siblings in the DOMconst appRoot = document.getElementById('app-root');const modalRoot = document.getElementById('modal-root');class Modal extends React.Component {constructor(props) {super(props);this.el = document.getElementById('div');}componentDidMount() {// The portal element is inserted in the DOM tree after// the Modal's children are mounted, meaning that children// will be mouted on a detached DOM node. If a child// compoennt requires to be attached to the DOM tree// immediately when mounted, for example to measure a// DOM node, or uses 'autoFocus' in a descendant, add// state to Modal and only render the children when Modal// is inserted in the DOM tree.modalRoot.appendChild(this.el);}componentWillUnmount() {modalRoot.removeChild(this.el);}render() {return ReactDOM.createPoral(this.props.children,this.el,);}}class Parent extends React.Component {constructor(props) {super(props);this.state = {clicks: 0};this.handleClick = this.handleClick.bind(this);}handlClick() {// This will fire when the button in child is clicked,// updating Parent's state, even though button// is not direct descendant in the DOM.this.setState(prevState => ({clicks: prevState.clicks + 1}));}render() {return (<div onClick={this.handleClick}><p>Number of clicks: {this.state.clicks}</p><p>Open up the browser DevToolsto observe that the Buttonis not a child of the divwith the onClick handler.</p><Modal><Child /></Modal></div>);}}function Child() {// The click event on this button will bubble up to parent,// because there is no 'onClick' attribute definedreturn (<div className="modal"><button>Click</button></div>);}ReactDOM.render(<Parent />, appRoot);
在父组件里捕获一个来自portal 的事件冒泡,允许不固有的依赖于portal 的更为灵活的抽象的开发。例如,若你在渲染一个<Modal /> 组件,父组件能够捕获其事件而无论其是否采用portal 实现。
