Portals
是reactjs16提供的官方解决方案,使得组件可以脱离父组件层级挂载在DOM树的任何位置。
用法
普通情况下,组件的render函数返回的元素会被挂载在它的父级组件上。
import DemoComponent from './DemoComponent';
render() {
// DemoComponent元素会被挂载在id为parent的div的元素上
return (
<div id="parent">
<DemoComponent />
</div>
);
}
然而,有些元素需要被挂载在更高层级的位置。最典型的应用场景:当父组件具有overflow: hidden
或者z-index
的样式设置时,组件有可能被其他元素遮挡,这个时候你就可以考虑要不要使用Portal使组件的挂载脱离父组件。例如:对话框,tooltip。
import DemoComponent from './DemoComponent';
render() {
// react会将DemoComponent组件直接挂载在真真实实的 dom 节点 domNode 上,生命周期还和16版本之前相同。
return ReactDOM.createPortal(
<DemoComponent />,
domNode,
);
}
事件冒泡
组件的挂载点虽然可以脱离父组件,但组件的事件通过冒泡机制仍可以传给父组件。
官方DEMO
// These two containers are siblings in the 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() {
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() {
// 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 DevTools
to observe that the button
is not a child of the div
with 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 defined
return (
<div className="modal">
<button>Click</button>
</div>
);
}
ReactDOM.render(<Parent />, appRoot);