这篇文章说说关于Protals
2个点:
- Portals的概念及使用
- Protals的事件冒泡
一、关于react
的Portal
插槽。根据官网的说法:Portals传送门
Portal
提供了一种将子节点渲染到存在于父组件以外的 DOM 节点的优秀的方案。
一个 portal 的典型用例是当父组件有 overflow: hidden 或 z-index 样式时,但你需要子组件能够在视觉上“跳出”其容器。例如,对话框、悬浮卡以及提示框。就是这种需要在某个组件中使用,但是视图上需要在全局的范围。
先来看下Portal
的用法:第一个参数(child)是任何可渲染的 React 子元素,例如一个元素,字符串或 fragment。第二个参数(container)是一个 DOM 元素。
ReactDOM.createPortal(child, container)
// child即子组件中的内容,如`<Modal><div>test</div><</Modal>`组件中的`<div>test</div>`
由于是在我现有测试项目的基础上改造,所以和官网例子稍微有点不同。不过是可以对照着看的:
App.js
// 这是app根组件,Routers渲染home页。也就是说,我们即将要渲染的弹窗会在id为`box`的`div`中。和渲染出的home平级。
import Routers from './router/router';
class App extends Component {
render () {
return (
<div className="test">
<div id="box"></div>
<Routers></Routers>
</div>
)
}
}
export default App;
home.js
/* eslint-disable */
import React from "react";
import Header from '../../component/header/header.js';
import Footer from '../../component/footer/footer';
import Sidebar from '../../component/sidebar/sidebar';
import './home.scss'
const modalRoot = document.getElementById('box'); // 获取`id`为`box`的`dom`
// 弹窗组件
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 Home extends Component {
constructor(props) {
super(props)
this.state = {
showModal: false,
}
}
handleShow = () => {
this.setState({
showModal: true,
})
}
handleHide = () => {
this.setState({
showModal: false,
})
}
getModal = () => {
console.log('这是Modal冒泡的事件')
}
render () {
const modal = this.state.showModal ? (
<Modal>
<div className="modal">
<div>
With a portal, we can render content into a different
part of the DOM, as if it were any other React child.
</div>
This is being rendered inside the #modal-container div.
<button onClick={this.handleHide}>Hide modal</button>
</div>
</Modal>
) : null;
return (
<div className="test_home" id="test-home" >
<div className="app" onClick={ this.getModal.bind(this) }>
This div has overflow: hidden.
<button onClick={ this.handleShow.bind(this) }>Show modal</button>
{modal}
</div>
</div>
)
}
}
export default Home
home.scss
.app {
height: 10em;
width: 10em;
background: lightblue;
overflow: hidden;
}
.modal {
background-color: rgba(0,0,0,0.5);
position: fixed;
height: 100%;
width: 100%;
top: 0;
left: 0;
display: flex;
align-items: center;
justify-content: center;
}
代码逻辑: 在App
根组件,放置了一个id为box
的dom
,同级放置了一个渲染home
页面的Router
组件。在home
组件中渲染modal
。
此时,modal
是home
组件的子节点。当showModal
为false
的时候,不进行渲染。为true
时,渲染Modal
组件。Modal
组件先是创建了一个div
并且插入到id为box
的div
中。利用ReactDOM.createPortal
将Modal
中的节点渲染到创建的div
中。
渲染结果
下面是注释掉modalRoot.removeChild(this.el);
这里
Modal
组件最终渲染在父组件Home
之外的App
组件id为box
的dom上。
建议:项目中实际使用时,可以将Modal
单独写一个文件。直接引用就可以。最大限度的复用可以降低冗余。
代码中Modal
组件,在componentDidMount
和componentWillUnmount
中分别添加和移除了dom。可以试一下,如果不移除,那么每渲染一次Modal
节点中就会多出一对div
节点。这样不符合我们优化的思想。
二、Protals
事件冒泡。上面的代码在Home
组件下className
为app
的dom上添加了getModal
事件。这时候我们运行程序,点击Hide modal
按钮,打开控制台,会发现打印出这是Modal冒泡的事件
。虽然Modal
最终是挂载到box
下面。但从Modal
中冒泡上来的事件依然能被{modal}
的所有parents
捕捉到。