Portal 提供了一种将子节点渲染到存在于父节点以外的DOM节点的优秀方案;
尽管 portal 可以被放置在 DOM 树中的任何地方,但在任何其他方面,其行为和普通的 React 子节点行为一致。由于 portal 仍存在于 React 树, 且与 DOM 树 中的位置无关,那么无论其子节点是否是 portal,像 context 这样的功能特性都是不变的。
这包含事件冒泡。一个从 portal 内部触发的事件会一直冒泡至包含 React 树*的祖先,即便这些元素并不是 *DOM 树 中的祖先
通俗来讲就是假设父节点app-root 通过 ReactDOM.createPortal 将它的子节点,挂载到它的兄弟节点modal-root上,父节点app-root的Partal组件仍然能狗捕获到该子节点冒泡上来的事件。
下面来实现一个建议的弹窗功能
创建Portal插槽组件MsgConten
import React from 'react'; import ReactDOM from "react-dom"; class Model extends React.Component{ constructor(props) { super(props); this.el = document.createElement('div'); this.modelRoot = document.getElementById('model-root'); } componentDidMount(){
//将插入弹窗的div放入到兄弟节点“model-root”中 this.modelRoot.appendChild(this.el); } //清理工作 componentWillUnmount() { this.modelRoot.removeChild(this.el); } render() { return ReactDOM.createPortal((//将创建好的弹窗插入到创建的div元素中 <MsgWindow closeModel={this.props.closeModel}/> ), this.el); } } //弹窗 function MsgWindow(props) { return( <div className={'cover'} style={{background:'grey',width:'200px',height:'200px',margin: '0 auto'}}> <button onClick={props.closeModel}>关闭</button> <div style={{color:'#ffffff'}}> 我是app-root的子节点,但被渲染在它的兄弟节点model-root上 </div> </div> ) } export default Model;
创建两个兄弟节点app-root和model-root
import React,{Component} from 'react'; import Model from './MsgConten'; class Portals extends Component{ state={ showMsg:false }; handleMsgWindow(){ this.setState({ showMsg:!this.state.showMsg }) } render() { return ( <> <div id={"app-root"} > {(this.state.showMsg == true)?<Model isShow={this.state.showMsg} closeModel={this.handleMsgWindow.bind(this)}/>:null} <button onClick={this.handleMsgWindow.bind(this)}>portals</button> </div> <div id={"model-root"}></div> </> ) } } export default Portals;
运行效果如下