React Portal的前世此生

在设计UI组件的过程当中不可避免的须要考虑模态窗的需求,好比dialog,tooltip这些,可是在React的框架下,咱们彷佛遇到了一些问题node

React下的modal需求

一般在设计这些模态窗的时候,会把整个DOM结构尽可能渲染在HTML位置比较顶层的地方,好比body。这样相对来讲样式的自由度会比较高。 可是在React的总体框架下,它的数据流向是自上而下的,若是你的modal中的内容依赖父级的数据,那可能就要将对应的组建挂载在依赖组建里面。固然能够在顶层组件管理modal数据或者直接上Redux,可是这对于一个定位成UI组件的设计上来讲,显示不够合理。这便促成了portal的想法出现---但愿modal组件 能跟正常的组件同样无论哪里须要就在哪里挂载,但实际DOM的位置确是另一个地方(好比React Bootstrap的Portal实现)app

React16以前的实现思路

首先组件不能渲染在它挂载的地方框架

render() {
return null;
}
复制代码

DOM真正渲染的位置,经过renderLayer来实现ui

renderLayer() {
//这里咱们假定render的执行是输出渲染内容
const { render } = this.props;
//构造DOM节点做为渲染内容的容器
if (!this.layer) {
this.layer = document.createElement('div');
document.body.appendChild(this.layer);
}
const layerElement = render();
this.layerElement = ReactDOM.unstable_renderSubtreeIntoContainer(this, layerElement, this.layer);
}

unrenderLayer() {
if (this.layer) {
React.unmountComponentAtNode(this.layer);
document.body.removeChild(this.layer);
this.layer = null;
}
}
复制代码

好了,咱们在各个生命周期里面调用它们就好了this

ReactDOM中提供了一个unstable_renderSubtreeIntoContainer,从名字上就能够发现,它并不推荐被使用,实际上它也的确表现得使人费解。spa

class Test extends React.Component {
componentDidMount() {
console.log('test');
setTimeout(() => this.forceUpdate(),5000)
}
componentDidUpdate() {
console.log('did update test')
}
render() {
return <p>test<A/></p>;
}
}

class B extends React.Component {
componentDidMount() {
console.log('did mount B')
}
componentDidUpdate() {
console.log('did update B')
}
render() {
return <a>some thing</a>;
}
}

class A extends React.Component {
componentDidMount() {
this.renderLayer();
console.log('did mount A')
}
componentDidUpdate() {
this.renderLayer();
console.log('did update A')
}
renderLayer() {
if (!this.layer) {
this.layer = document.createElement('div');
document.body.appendChild(this.layer);
}
ReactDOM.unstable_renderSubtreeIntoContainer(this, <B/>, this.layer);
}
render() {
return null;
}
}

ReactDOM.render(<Test />, document.getElementById('app')); 复制代码

按咱们对React父子组件间生命周期的执行状况上理解应当输出 https://codepen.io/anon/pen/GQRaEo?editors=1112设计

"did mount B"
"did mount A"
"test"
"did update B"
"did update A"
"did update test"
复制代码

而实际的结果倒是code

"did mount B"
"did mount A"
"test"
"did update A"
"did update test"
"did update B"
复制代码

显然在初始化的时候事情仍是符合咱们预期的 但是在执行更新组件的时候,生命周期的执行便显得很混乱,在React16的版本中这个问题获得了修复,但执行的结果显然也不是咱们最终想要的 https://codepen.io/anon/pen/MQWdPq?editors=1111component

"did mount A"
"test"
"did mount B"
"did update A"
"did update test"
"did update B"
复制代码

React Portal的出现完全解决了这方面的问题xml

React Portal

终于进入主题,先看看它是如何使用的

const node = document.createElement('div');
document.body.appendChild(this.node);
...

render() {
return createPortal(
<div class="dialog"> {this.props.children} </div>, //须要渲染的内容
node //渲染内容的容器DOM
);
}
复制代码

除了node节点在一些场景下须要释放以外,你已经不须要在其余生命周期里面擦屁股了 让咱们在回到以前生命周期执行上的问题 https://codepen.io/anon/pen/Jpjqwg?editors=1111 结果的执行跟咱们正常组件保持了一致,不再用担忧一些依赖子组件完成更新后的监听或操做会出现异常状况了。 除此以外React Portal还新增了一个事件冒泡的实现

<div onClick={handleClick}>
<Dialog/>
</div>
复制代码

若是在React16以前的实现方式,点击Dialog组件里面的内容handleClick是不会被触发,但经过React Portal实现的挂载方式将会发生冒泡。 这个特性见仁见智吧,通常状况下感受也不会用到。

总结

总之React Portal的实现对于modal的实现是一个重大的更新,同时也避免了组件间生命周期的执行混乱。

相关文章
相关标签/搜索