What do React Portals solve?

心不动则不痛 提交于 2021-02-07 19:48:19

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!