We want to hear from you!Take our 2021 Community Survey!
This site is no longer updated.Go to react.dev

Портали

These docs are old and won’t be updated. Go to react.dev for the new React docs.

These new documentation pages teach modern React and include live examples:

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

ReactDOM.createPortal(child, container)

Перший аргумент (child) — це будь-який React-компонент, який може бути відрендерений, такий як елемент, строка або фрагмент. Другий аргумент (container) — це DOM-елемент.

Застосування

Зазвичай, коли ви повертаєте елемент з рендер-методу компонента, він монтується в DOM як дочірній елемент найближчого батьківського вузла:

render() {
  // React монтує новий div і рендерить в нього дочірні елементи
  return (
    <div>      {this.props.children}
    </div>  );
}

Однак іноді потрібно помістити дочірній елемент в інше місце в DOM:

render() {
  // React *не* створює новий div. Він рендерить дочірні елементи в `domNode`.
  // `domNode` — це будь-який валідний DOM-вузол, що знаходиться в будь-якому місці в DOM.
  return ReactDOM.createPortal(
    this.props.children,
    domNode  );
}

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

Примітка:

При роботі з порталами пам’ятайте, що потрібно приділити увагу управлінню фокусом за допомогою клавіатури.

Для модальних діалогів переконайтеся, що будь-який користувач буде здатний взаємодіяти з ними, слідуючи практикам розробки модальних вікон WAI-ARIA.

Спробувати на CodePen

Спливання подій через портали

Як вже було сказано, портал може перебувати в будь-якому місці DOM-дерева. Незважаючи на це, у всіх інших аспектах він поводиться як звичайний React-компонент. Такі можливості, як контекст, працюють звичним чином, навіть якщо нащадок є порталом, оскільки сам портал все ще знаходиться в React-дереві, незважаючи на його розташування в DOM-дереві.

Так само працює і спливання подій. Подія, згенерована зсередини порталу, буде поширюватися до батьків, що містяться у React-дереві, навіть якщо ці елементи не є батьківськими в DOM-дереві. Припустимо, що є наступна HTML-структура:

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

Батьківський компонент в #app-root зможе зловити неперехоплену спливаючу подію з сусіднього вузла #modal-root.

// Це два сусідніх контейнера в 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() {
    // Елемент порталу додається в DOM-дерево після того, як
    // дочірні компоненти Modal будуть змонтовані, а це означає,
    // що дочірні компоненти будуть монтуватися на окремому DOM-вузлі.
    // Якщо дочірній компонент повинен бути приєднаний до DOM-дерева
    // відразу при підключенні, наприклад, для вимірювань DOM-вузла
    // або виклику в дочірньому елементі 'autoFocus', додайте в компонент Modal
    // стан і рендеріть дочірні елементи тільки тоді, коли
    // компонент Modal вже вставлений в DOM-дерево.
    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() {    // Ця функція буде викликана при натисканні на кнопку в компоненті Child    // і оновить стан компонента Parent, незважаючи на те,    // що кнопка не є прямим нащадком в DOM.    this.setState(state => ({      clicks: state.clicks + 1    }));  }
  render() {
    return (
      <div onClick={this.handleClick}>        <p>Кількість натискань: {this.state.clicks}</p>
        <p>
          Відкрийте DevTools браузера,
          щоб переконатися, що кнопка
          не є нащадком блоку div
          з обробником onClick.
        </p>
        <Modal>          <Child />        </Modal>      </div>
    );
  }
}

function Child() {
  // Подія натискання на цю кнопку буде спливати вгору до батьківського елемента,  // тому що не визначено атрибут "onClick"  return (
    <div className="modal">
      <button>Натисніть</button>    </div>
  );
}

const root = ReactDOM.createRoot(appRoot);
root.render(<Parent />);

Спробувати на CodePen

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