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

  • Создание модальных окон или диалогов
  • Добавление уведомлений или предупреждений
  • Отображение элементов в другом контексте DOM, например, в <head> документа

Чтобы использовать порталы, вам необходимо установить пакет react-dom:

npm install react-dom

Затем вы можете использовать метод ReactDOM.createPortal() для создания портала. Этот метод принимает два аргумента: элемент, который нужно отобразить, и элемент DOM, в котором этот элемент должен быть отрисован.

Вот пример простого портала, отображающего модальное окно:

import React from 'react';
import ReactDOM from 'react-dom';
function Modal() {
  return ReactDOM.createPortal(
    <div className="modal">
      <p>This is a modal</p>
    </div>,
    document.body
  );
}

В этом примере компонент Modal визуализируется в <body> документа, за пределами его родительского компонента.

Использование порталов с состоянием

Чтобы использовать порталы с состоянием, вам необходимо создать компонент-контейнер, который управляет состоянием и отображает портал. Вот пример компонента ModalContainer, который управляет видимостью модального окна:

import React from 'react';
import ReactDOM from 'react-dom';
function Modal({ isOpen }) {
  return ReactDOM.createPortal(
    <div className={`modal ${isOpen ? 'is-open' : ''}`}>
      <p>This is a modal</p>
    </div>,
    document.body
  );
}
import React, { useState } from 'react';
import Modal from './Modal';
function ModalContainer() {
  const [isOpen, setIsOpen] = useState(false);
  return (
    <>
      <button onClick={() => setIsOpen(true)}>Open Modal</button>
      <Modal isOpen={isOpen} />
    </>
  );
}

В этом примере компонент ModalContainer управляет состоянием isOpen, которое определяет, является ли модальное окно видимым или нет. Когда пользователь нажимает кнопку «Открыть модальное окно», состояние isOpen устанавливается на true, что приводит к отображению модального окна.

Закрытие модального окна

Чтобы закрыть модальное окно, вам нужно добавить способ его закрытия пользователем. Вот пример компонента Modal с кнопкой закрытия:

import React, { useState } from 'react';
import ReactDOM from 'react-dom';
function Modal({ isOpen, onClose }) {
  return ReactDOM.createPortal(
    <div className={`modal ${isOpen ? 'is-open' : ''}`}>
      <button onClick={onClose}>Close</button>
      <p>This is a modal</p>
    </div>,
    document.body
  );
}
function ModalContainer() {
  const [isOpen, setIsOpen] = useState(false);
  function handleClose() {
    setIsOpen(false);
  }
  return (
    <>
      <button onClick={() => setIsOpen(true)}>Open Modal</button>
      <Modal isOpen={isOpen} onClose={handleClose} />
    </>
  );
}

В этом примере компонент Modal включает кнопку закрытия, которая при нажатии вызывает свойство onClose. Компонент ModalContainer передает функцию handleClose компоненту Modal в качестве реквизита onClose, который устанавливает состояние isOpen в false и скрывает модальное окно.

Передовые методы

Вот несколько продвинутых методов использования порталов:

Доступ к узлу портала

Если вам нужно получить доступ к узлу DOM портала, вы можете использовать реквизит ref:

import React, { useRef } from 'react';
import ReactDOM from 'react-dom';
function Modal({ isOpen }) {
  const modalRef = useRef();
  return ReactDOM.createPortal(
    <div className={`modal ${isOpen ? 'is-open' : ''}`} ref={modalRef}>
      <p>This is a modal</p>
    </div>,
    document.body
  );
}

В этом примере ссылка modalRef прикреплена к элементу <div>, который визуализируется как портал. Вы можете получить доступ к узлу DOM портала, используя свойство current ссылки:

console.log(modalRef.current); // The DOM node of the portal

Обновление элемента портала

Если вам нужно обновить отображаемый на портале элемент, вы можете использовать метод ReactDOM.unstable_renderSubtreeIntoContainer(). Этот метод позволяет обновить отображаемый на портале элемент без размонтирования и повторного монтирования компонента портала.

Вот пример компонента Modal, который использует метод unstable_renderSubtreeIntoContainer() для обновления элемента, отображаемого на портале:

import React, { useEffect, useRef } from 'react';
import ReactDOM from 'react-dom';
function Modal({ isOpen, children }) {
  const modalRef = useRef();
  const portalElement = useRef();
  useEffect(() => {
    portalElement.current = document.createElement('div');
    document.body.appendChild(portalElement.current);
    return () => {
      document.body.removeChild(portalElement.current);
    };
  }, []);
  useEffect(() => {
    ReactDOM.unstable_renderSubtreeIntoContainer(
      modalRef.current,
      <div className={`modal ${isOpen ? 'is-open' : ''}`}>
        <button onClick={onClose}>Close</button>
        {children}
      </div>,
      portalElement.current
    );
  }, [isOpen, children]);
  return ReactDOM.createPortal(
    <div ref={modalRef} />,
    portalElement.current
  );
}

В этом примере компонент Modal использует хук useEffect() для создания нового элемента DOM, когда компонент монтируется, и для удаления элемента, когда компонент размонтирован. Метод unstable_renderSubtreeIntoContainer() используется для обновления элемента, отображаемого на портале, при изменении реквизита isOpen или children.

Обработка событий клавиатуры

Если вы используете порталы для создания модальных окон или диалогов, вы, вероятно, захотите обрабатывать события клавиатуры, чтобы пользователь мог закрыть модальное окно с помощью клавиши Escape. Вот пример компонента Modal, который обрабатывает события клавиатуры:

import React, { useEffect, useRef } from 'react';
import ReactDOM from 'react-dom';
function Modal({ isOpen, onClose }) {
  const modalRef = useRef();
  const portalElement = useRef();
  useEffect(() => {
    portalElement.current = document.createElement('div');
    document.body.appendChild(portalElement.current);
    return () => {
      document.body.removeChild(portalElement.current);
    };
  }, []);
  useEffect(() => {
    const handleKeyDown = (event) => {
      if (event.key === 'Escape') {
        onClose();
      }
    };
    document.addEventListener('keydown', handleKeyDown);
    return () => {
      document.removeEventListener('keydown', handleKeyDown);
    };
  }, [onClose]);
  useEffect(() => {
    ReactDOM.unstable_renderSubtreeIntoContainer(
import React, { useEffect, useRef } from 'react';
import ReactDOM from 'react-dom';
function Modal({ isOpen, onClose }) {
  const modalRef = useRef();
  const portalElement = useRef();
  useEffect(() => {
    portalElement.current = document.createElement('div');
    document.body.appendChild(portalElement.current);
    return () => {
      document.body.removeChild(portalElement.current);
    };
  }, []);
  useEffect(() => {
    const handleKeyDown = (event) => {
      if (event.key === 'Escape') {
        onClose();
      }
    };
    document.addEventListener('keydown', handleKeyDown);
    return () => {
      document.removeEventListener('keydown', handleKeyDown);
    };
  }, [onClose]);
  useEffect(() => {
    ReactDOM.unstable_renderSubtreeIntoContainer(
      modalRef.current,
      <div className={`modal ${isOpen ? 'is-open' : ''}`}>
        <button onClick={onClose}>Close</button>
        <p>This is a modal</p>
      </div>,
      portalElement.current
    );
  }, [isOpen]);
  return ReactDOM.createPortal(
    <div ref={modalRef} />,
    portalElement.current
  );
}
function ModalContainer() {
  const [isOpen, setIsOpen] = useState(false);
  function handleOpen() {
    setIsOpen(true);
  }
  function handleClose() {
    setIsOpen(false);
  }
  return (
    <>
      <button onClick={handleOpen}>Open Modal</button>
      <Modal isOpen={isOpen} onClose={handleClose}

Подведение итогов

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

Я надеюсь, что эта статья помогла вам понять, как работают порталы и как эффективно использовать их в ваших собственных проектах. Удачного кодирования!