When designing UI components, modal Windows, such as dialogs and tooltips, need to be taken into account, but with React we seem to be running into some problems

The Modal requirement under React

These modal Windows are usually designed to render the entire DOM structure at the top level of the HTML, such as the body. So you have relatively high degrees of freedom. However, in the overall framework of React, the data flow is top-down. If the content in Modal depends on the parent data, you may need to mount the corresponding component in the dependency component. It is possible to manage modal data in a top-level component or directly onto Redux, but this is not a reasonable display for a design that is positioned as a UI component. This led to the idea of portal – the modal component is expected to be mounted wherever it is needed just like a normal component, but the actual DOM location is in a different place (such as the React Bootstrap Portal implementation).

Implementation ideas prior to React16

First the component cannot be rendered where it is mounted

render() {
return null;
}
Copy the code

RenderLayer is used to specify where the DOM is actually rendered

renderLayer() {
// Here we assume that the render execution outputs the render content
const { render } = this.props;
// Construct DOM nodes as containers for rendering content
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; }}Copy the code

Well, we just call them in each life cycle

Provides a unstable_renderSubtreeIntoContainer ReactDOM, can be found from the name, it is not recommended to be used, in fact, it is really confusing.

class Test extends React.Component {
componentDidMount() {
console.log('test');
setTimeout((a)= > 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'));
Copy the code

According to the us to React component life cycle between father and son on the implementation of understanding should be output at https://codepen.io/anon/pen/GQRaEo? editors=1112

"did mount B"
"did mount A"
"test"
"did update B"
"did update A"
"did update test"
Copy the code

And the actual result is

"did mount B"
"did mount A"
"test"
"did update A"
"did update test"
"did update B"
Copy the code

Obviously things were going as expected when we initialized them, but when we executed the updated components, the life cycle execution became messy. This was fixed in Version 16, But the result of the execution obviously it’s not what we ultimately want to https://codepen.io/anon/pen/MQWdPq? editors=1111

"did mount A"
"test"
"did mount B"
"did update A"
"did update test"
"did update B"
Copy the code

React Portal solves this problem completely

React Portal

Finally, how does it work

const node = document.createElement('div');
document.body.appendChild(this.node); . render() {return createPortal(
<div class="dialog">
{this.props.children}
</div>.// The content to render
node // Render the container DOM of the content
);
}
Copy the code

Except the node node in some scenarios need to release, you already did not need to wipe a bottom in the other life cycle Let us before back to the life cycle of execution problems on https://codepen.io/anon/pen/Jpjqwg? Editors =1111 results are executed in the same way as our normal component, and we no longer have to worry about exceptions to some listeners or operations that rely on child components to complete the update. React Portal also adds an implementation for event bubbling

<div onClick={handleClick}>
<Dialog/>
</div>
Copy the code

If implemented before React16, clicking on the contents of the Dialog component handleClick would not be triggered, but the mount mode implemented via React Portal would bubble. This feature is in the eye of the beholder, and is not used by the senses in general.

conclusion

In summary, the React Portal implementation is a major update to the Modal implementation, and also avoids messy execution of the lifecycle between components.