问题
From the docs of react portal:
A typical use case for portals is when a parent component has an overflow: hidden or z-index style, but you need the child to visually “break out” of its container. For example, dialogs, hovercards, and tooltips.
The suggested solution is to use:
// this line is rendered in `Portal1` component,
// which is rendered in `Parent1` component.
ReactDOM.createPortal(Child1, Container1)
I don't understand what does it solves. Why making Child1
child of Container1
instead of Parent1
helps?
My question maybe not clear so if it doesn't -> How does this solution differ from other solutions for creating "dialogs, hovercards, and tooltips"?
回答1:
When you initialise a React application, ReactDOM
tells one DOM
container that all its React components will be rendered under this DOM
. This makes React do all rendering processing.
Sometimes you need to control a React Component to render as a child to a different DOM
element, and continue to interact with your React application. This is why we use React Portals
As React creates virtual elements under the hood, you cannot convert the into DOM
elements and insert them directly into the DOM
. React Portals allows to you pass a React Elements and specify the container DOM for the React Element
Here is an example:
You have a Modal
component which renders a div
element in the center.
function Modal() {
return (
<div style={{ position: 'absolute', left: '50%'}}>
Message
</div>
);
}
One puts your Modal
component inside a div
of relative position.
<div style={{ position: 'relative', left: 100 }}>
<Modal />
</div>
The problem is when Modal
component is rendered, its position is relative to parent div
's position but you need to show it at the centre of window.
In order to solve this problem, you can append your Modal
component directly to the body
element with a portal
Here is the solution with Portals.
function ModalRenderer() {
return (
React.createPortal(
<Modal />,
document.body
)
);
}
And use ModalRenderer
component anywhere inside your application.
<div style={{ position: 'relative', left: 100 }}>
<ModalRenderer />
</div>
ModalRenderer
has the container element for the Modal
which is outside of the DOM tree, but still within the React Application tree
回答2:
In React V15,we can only add children dom into the father dom.That means, if you want to have an element, you have to create a new div.Like this:
<div>
{this.props.children}
</div>
In React v16,don't need to create a new div.We can use portal to add the children element to any dom in the dom tree.
ReactDOM.createPortal(
this.props.children,
domNode
);
overflow: hidden or z-index style
If a parent component has an overflow: hidden or z-index style, and the children element type is dialogs, hovercards, tooltips and so on,these should be on the upper layer of the father element, meaning break out.But they maybe shade by the father component.
So createPortal offers a better option.It can load on the upper component of the father component.After mounting the element to another dom,it won't be sheltered.
Event and bubble up
Even the component mounted on another component, event can budde up to the father component.
回答3:
One good case is to separate CSS concerns.
Here is an example:
HTML
<div id="app-root"></div>
<div id="modal-root"></div>
CSS
.app {
position: fixed;
height: 100%;
width: 100%;
top: 0;
left: 0;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
}
.modal {
background-color: rgba(0,0,0,0.5);
}
Babel
const appRoot = document.getElementById('app-root')
const modalRoot = document.getElementById('modal-root')
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
showModal: false
}
this.handleShow = this.handleShow.bind(this);
this.handleHide = this.handleHide.bind(this);
}
handleShow() {
this.setState({
showModal: true
})
}
handleHide() {
this.setState({
showModal: false
})
}
render() {
const modal = this.state.showModal ? (
<Modal>
<div className="modal">I am no longer centered!</div>
</Modal>
) : null
return (
<div className='app'>
Basic
<button onClick={this.handleShow}>
Show Modal
</button>
{modal}
</div>
)
}
}
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
)
}
}
ReactDOM.render(<App />, appRoot)
Observations:
1. The main text has fixed
position and is centered
2. When we click the button, the text popped up. Notice the text is no longer centered!
3. If we had used it to something like this:
<div className='app'>
Basic
<button onClick={this.handleShow}>
Show Modal
</button>
<div className="modal">I am centered ;(</div>
</div>
Notice the text is centered. Modal works by going to modal-root
and attach that DOM element there. You can have your own CSS under modal-root
hood, separate from parent component (app-root
).
You are now no longer obligated to attach your "child" components under your parent's. In this case, you (app-root
) are attaching it to its sibling (modal-root
). You can totally attach it to document.body
, or whatever element you wanted to. Another perk is, like other user mentioned, event bubbling happens as if that child component is their own child.
A Parent component in #app-root would be able to catch an uncaught, bubbling event from the sibling node #modal-root. Source
来源:https://stackoverflow.com/questions/53692251/what-do-react-portals-solve