Unetway

ReactJS - Порталы (Portals)

Порталы предоставляют первоклассный способ отображения дочерних элементов в узел DOM, который существует вне иерархии DOM родительского компонента.

ReactDOM.createPortal(child, container)

Первым аргументом (child) является любой рендерируемый дочерний элемент React, такой как элемент, строка или фрагмент. Второй аргумент (container) является элементом DOM.

Применение

Обычно, когда вы возвращаете элемент из метода рендеринга компонента, он монтируется в DOM как дочерний элемент ближайшего родительского узла:

render() {
  // React mounts a new div and renders the children into it
  return (
    <div>
      {this.props.children}
    </div>
  );
}

Однако иногда полезно вставлять ребенка в другое место в DOM:

render() {
  // React does *not* create a new div. It renders the children into `domNode`.
  // `domNode` is any valid DOM node, regardless of its location in the DOM.
  return ReactDOM.createPortal(
    this.props.children,
    domNode
  );
}

Типичный пример использования для порталов - это когда родительский компонент имеет стиль overflow: hidden или z-index стиль, но вам нужен ребенок, чтобы визуально «вырваться» из его контейнера. Например, диалоги, наводки и всплывающие подсказки.

Прохождение событий через порталы

Несмотря на то, что портал может быть где угодно в дереве DOM, он ведет себя как обычный ребенок Реагента любым другим способом. Такие функции, как контекст, работают точно так же, независимо от того, является ли ребенок порталом, поскольку портал все еще существует в дереве React независимо от положения в дереве DOM .

Это включает в себя событие пузыря. Событие, запущенное изнутри портала, будет распространяться на предков в содержащем дереве React , даже если эти элементы не являются предками в дереве DOM . Предполагая следующую структуру HTML:

<html>
  <body>
    <div id="app-root"></div>
    <div id="modal-root"></div>
  </body>
</html>

Parent компонент #app-root будет в состоянии поймать неперехваченное, кипящее событие из узла сиблингов #modal-root.

// These two containers are siblings in the DOM
const appRoot = document.getElementById('app-root');
const modalRoot = document.getElementById('modal-root');

class Modal extends React.Component {
  constructor(props) {
    super(props);
    this.el = document.createElement('div');
  }

  componentDidMount() {
    // The portal element is inserted in the DOM tree after
    // the Modal's children are mounted, meaning that children
    // will be mounted on a detached DOM node. If a child
    // component requires to be attached to the DOM tree
    // immediately when mounted, for example to measure a
    // DOM node, or uses 'autoFocus' in a descendant, add
    // state to Modal and only render the children when Modal
    // is inserted in the DOM tree.
    modalRoot.appendChild(this.el);
  }

  componentWillUnmount() {
    modalRoot.removeChild(this.el);
  }

  render() {
    return ReactDOM.createPortal(
      this.props.children,
      this.el,
    );
  }
}

class Parent extends React.Component {
  constructor(props) {
    super(props);
    this.state = {clicks: 0};
    this.handleClick = this.handleClick.bind(this);
  }

  handleClick() {
    // This will fire when the button in Child is clicked,
    // updating Parent's state, even though button
    // is not direct descendant in the DOM.
    this.setState(prevState => ({
      clicks: prevState.clicks + 1
    }));
  }

  render() {
    return (
      <div onClick={this.handleClick}>
        <p>Number of clicks: {this.state.clicks}</p>
        <p>
          Open up the browser DevTools
          to observe that the button
          is not a child of the div
          with the onClick handler.
        </p>
        <Modal>
          <Child />
        </Modal>
      </div>
    );
  }
}

function Child() {
  // The click event on this button will bubble up to parent,
  // because there is no 'onClick' attribute defined
  return (
    <div className="modal">
      <button>Click</button>
    </div>
  );
}

ReactDOM.render(<Parent />, appRoot);

Захват события, пузырящегося с портала в родительском компоненте, позволяет создавать более гибкие абстракции, которые по своей сути не зависят от порталов. Например, если вы визуализируете <Modal /> компонент, родитель может записывать свои события независимо от того, реализован ли он с помощью порталов.