简要介绍:React16.0中发布了很多新特性,我们来看portal,React提供了一个顶级API—portal,用来将子节点渲染到父节点之外的dom节点
1.基本用法
(1)在React15.X版本中,我们只能讲子节点在父节点中渲染,基本用法如下:
render() {
// React需要创建一个新的div来包含子节点
return (
<div>
{this.props.children}
</div>
);
}
但是如果需要将子节点插入到父节点之外的dom呢,React15.x及之前都没有提供这个功能的API。
(2)React16.0中的portal
render() {
// React不需要创建一个新的div去包含子元素,直接将子元素渲染到另一个
//dom节点中
//这个dom节点可以是任何有效的dom节点,无论其所处于dom树中的哪个位置
return ReactDOM.createPortal(
this.props.children,
domNode,
);
}
- Portal中的dom节点必须是有效的节点
ReactDOM.createPortal函数的第二个参数,是被插入的dom节点,并且这个dom节点是有效的节点,而不能是通过ref获得并传递的virtual dom节点,举例来说:
class MyTestableClass extends React.Component{
constructor(){
super();
this.state={
sprop:''
}
}
render(){
return <div>
<Left ref={(input)=>{this.left=input}}>我是左边的信息</Left>
<Right sprop={this.state.sprop}>我是右边的信息</Right>
</div>
}
componentDidMount(){
var left=this.left;
var state=this.state;
this.setState({
sprop:left
})
}
}
ReactDom.render(
<MyTestableClass/>,document.getElementById('app')
)
在整个组件中,我们将ref获得的子节点Left,通过props传递给了子节点Right,然后在Right子节点中,通过:
class Right extends React.Component{
constructor(){
super();
}
render(){
var props=this.props;
if(this.props.sprop){
return <div className="right">{this.props.children}</div>
}else{
return ReactDOM.createPortal(
this.props.children,
this.props.sprop
);
}
}
componentWillReceiveProps(nextProp){
console.log(nextProp.sprop);
}
}
我们尝试将子节点插入到通过props传递过来的另一个节点this.props.sprop中,因为this.props.sprop并不是一个valid dom node,它是一个virtual dom node,因此会报错:
//Target container is not a DOM element.
3.通过Portals实现事件冒泡
虽然一个portal可以插入到任何一个存在dom树中,但是通过Portal节点插入到其他dom中的节点,跟其他的React普通的子节点表现相同。在父节点下,通过Portal插入的子节点也可以共享context。
对于事件冒泡,从Portal节点中触发的事件,虽然可能改变了节点所处的位置,但是在HTML结构中的父包含节点是可以拿到这个事件的。
<html>
<body>
<div id="app-root"></div>
<div id="modal-root"></div>
</body>
</html>
在上面的HTML结构中,通过Portal, 在Button真实位置是包含在“modal-root”下,但是因为在HTML的结构中Button所在的父节点的HTML结构是包含“app-root”中的,因此在“modal-root”中出发的事件,也可以冒泡到“app-root”中。