React Protals
Portal
提供了一种将子节点渲染到存在于父组件以外的 DOM
节点的优秀的方案。
ReactDOM.createPortal(child, container)
注意createPortal
是ReactDOM
的api,需要引入react-dom
包
第一个参数(child
)是任何可渲染的 React
子元素,例如一个元素,字符串或 fragment
。第二个参数(container
)是一个 DOM
元素。
通常来讲,当你从组件的 render
方法返回一个元素时,该元素将被挂载到 DOM
节点中离其最近的父节点:
然而,有时候将子元素插入到 DOM
节点中的不同位置也是有好处的,一个 portal
的典型用例是当父组件有 overflow: hidden
或 z-index
样式时,但你需要子组件能够在视觉上“跳出”其容器。例如,对话框、悬浮卡以及提示框:
下面是一个demo:
import React from 'react'
import ReactDOM from 'react-dom'
// Portal 提供了一种将子节点渲染到存在于父组件以外的 DOM 节点的优秀的方案。
// antd中的Dialog就是用的这个方案
class Modal extends React.Component {
constructor(props) {
super(props)
this.$el = document.createElement('div')
}
componentDidMount() {
document.body.appendChild(this.$el)
}
componentWillUnmount() {
console.log(this.$el)
document.body.removeChild(this.$el)
}
render () {
return <div>
{ReactDOM.createPortal(this.props.children, this.$el)}
</div>
}
}
class Comp extends React.Component {
constructor(props){
super(props)
this.state = {
showModal: false
}
}
handleClick = () => {
this.setState({
showModal: !this.state.showModal
})
}
render() {
return <div>
<button onClick={this.handleClick}>show</button>
{this.state.showModal ? <Modal>
<div style={{width: '100px', height: '100px', backgroundColor: 'gray'}}>
<button onClick={this.handleClick}>hide</button>
</div>
</Modal> : null}
</div>
}
}
export default Comp
尽管 portal
可以被放置在 DOM
树中的任何地方,但在任何其他方面,其行为和普通的 React
子节点行为一致。由于 portal
仍存在于 React
树, 且与 DOM
树中的位置无关,那么无论其子节点是否是 portal
,像 context
这样的功能特性都是不变的。
这包含事件冒泡。一个从 portal
内部触发的事件会一直冒泡至包含 React
树的祖先,即便这些元素并不是 DOM
树中的祖先。
假设我们在Modal
父元素上绑定一个点击事件,则Modal
内部的点击会一直冒泡到其React
树的父元素上
import React from 'react'
import ReactDOM from 'react-dom'
class Modal extends React.Component {
constructor(props) {
super(props)
this.$el = document.createElement('div')
}
componentDidMount() {
document.body.appendChild(this.$el)
}
componentWillUnmount() {
console.log(this.$el)
document.body.removeChild(this.$el)
}
render () {
return ReactDOM.createPortal(this.props.children, this.$el)
}
}
class Comp extends React.Component {
constructor(props){
super(props)
this.state = {
showModal: false,
count: 0
}
}
handleClick = (e) => {
this.setState({
showModal: !this.state.showModal
})
e.stopPropagation()
}
handlePopClick = () => {
this.setState({
count: this.state.count + 1
})
}
render() {
// 在这里绑定事件,每次都会使count +1,
return <div onClick={this.handlePopClick}>
<button onClick={this.handleClick}>show</button>
count: {this.state.count}
{this.state.showModal ? <Modal>
<div style={{width: '100px', height: '100px', backgroundColor: 'gray'}}>
<button onClick={this.handleClick}>hide</button>
</div>
</Modal> : null}
</div>
}
}
export default Comp
可以看到虽然Modal
被渲染到了body
上,但是父元素上还是能捕获到冒泡上来的事件。
可以理解为Portals
只是把dom
渲染到了其他地方,但是从代码结构上来看,还是处在当前父子组件的上下文中
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 289211569@qq.com