← Назад к вопросам

Используешь ли порталы

2.0 Middle🔥 141 комментариев
#Soft Skills и рабочие процессы

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI2 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Используешь ли порталы

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

Что такое Portal

Обычно React рендерит компоненты в том месте, где они объявлены в иерархии. Портал позволяет рендерить компонент в другое место DOM дерева:

import { createPortal } from 'react-dom';

function Modal({ isOpen, children }) {
  if (!isOpen) return null;
  
  // Рендерим компонент в элемент с id="modal-root"
  return createPortal(
    <div className="modal-overlay">
      <div className="modal-content">
        {children}
      </div>
    </div>,
    document.getElementById('modal-root')
  );
}

Где используются порталы

  1. Модальные окна - нужно разместить поверх всего контента, независимо от z-index родителя:
function DeleteConfirmModal({ isOpen, onConfirm, onCancel }) {
  return createPortal(
    <div className="modal-overlay" onClick={onCancel}>
      <div className="modal" onClick={(e) => e.stopPropagation()}>
        <h2>Delete item?</h2>
        <p>Are you sure you want to delete this item?</p>
        <button onClick={onConfirm}>Delete</button>
        <button onClick={onCancel}>Cancel</button>
      </div>
    </div>,
    document.body
  );
}
  1. Тосты и уведомления - независимо от родителя:
function Toast({ message, type = 'info' }) {
  return createPortal(
    <div className={`toast toast-${type}`}>
      {message}
    </div>,
    document.getElementById('toast-container')
  );
}

// В компоненте
function Page() {
  const [toasts, setToasts] = useState([]);
  
  const showToast = (message, type) => {
    setToasts([...toasts, { id: Date.now(), message, type }]);
  };
  
  return (
    <div>
      <button onClick={() => showToast('Success!', 'success')}>
        Show Toast
      </button>
      {toasts.map(toast => (
        <Toast key={toast.id} message={toast.message} type={toast.type} />
      ))}
    </div>
  );
}
  1. Дропдауны и меню - нужно выходить за рамки overflow: hidden родителя:
function Dropdown({ trigger, children }) {
  const [isOpen, setIsOpen] = useState(false);
  const [position, setPosition] = useState({ top: 0, left: 0 });
  const triggerRef = useRef(null);
  
  useEffect(() => {
    if (isOpen && triggerRef.current) {
      const rect = triggerRef.current.getBoundingClientRect();
      setPosition({
        top: rect.bottom + window.scrollY,
        left: rect.left + window.scrollX,
      });
    }
  }, [isOpen]);
  
  return (
    <div>
      <button ref={triggerRef} onClick={() => setIsOpen(!isOpen)}>
        {trigger}
      </button>
      {isOpen && createPortal(
        <div
          className="dropdown"
          style={{
            position: 'absolute',
            top: `${position.top}px`,
            left: `${position.left}px`,
          }}
          onClick={() => setIsOpen(false)}
        >
          {children}
        </div>,
        document.body
      )}
    </div>
  );
}

Структура HTML для порталов

В index.html нужно создать корневые элементы для порталов:

<!DOCTYPE html>
<html>
  <head>
    <title>App</title>
  </head>
  <body>
    <!-- Основное приложение -->
    <div id="root"></div>
    
    <!-- Модальные окна -->
    <div id="modal-root"></div>
    
    <!-- Тосты -->
    <div id="toast-container"></div>
    
    <!-- Дропдауны -->
    <div id="dropdown-root"></div>
  </body>
</html>

Преимущества использования порталов

  1. Избегаешь z-index проблем - модалка всегда на верху, если рендерится в body

  2. Избегаешь overflow hidden - дропдаун не обрезается родителем

  3. Чистая архитектура - логика модалки отделена от контента

  4. Event bubbling работает - события всё равно идут от элемента, где они произойдут

function Parent() {
  const handleClick = (e) => {
    console.log('Parent clicked');
  };
  
  return (
    <div onClick={handleClick}>
      {/* Если кликнуть на модалку, родитель НЕ получит событие */}
      <ChildWithPortal />
    </div>
  );
}

function ChildWithPortal() {
  return createPortal(
    <div onClick={(e) => {
      e.stopPropagation();
      console.log('Modal clicked');
    }}>
      Click me
    </div>,
    document.body
  );
}

Практический пример: система уведомлений

// Context для управления уведомлениями
const NotificationContext = React.createContext();

function NotificationProvider({ children }) {
  const [notifications, setNotifications] = useState([]);
  
  const addNotification = (message, type = 'info', duration = 3000) => {
    const id = Date.now();
    setNotifications(prev => [...prev, { id, message, type }]);
    
    if (duration) {
      setTimeout(() => removeNotification(id), duration);
    }
  };
  
  const removeNotification = (id) => {
    setNotifications(prev => prev.filter(n => n.id !== id));
  };
  
  return (
    <NotificationContext.Provider value={{ addNotification, removeNotification }}>
      {children}
      <NotificationContainer notifications={notifications} />
    </NotificationContext.Provider>
  );
}

function NotificationContainer({ notifications }) {
  return createPortal(
    <div className="notification-container">
      {notifications.map(notif => (
        <Notification
          key={notif.id}
          message={notif.message}
          type={notif.type}
        />
      ))}
    </div>,
    document.getElementById('notification-root')
  );
}

// Использование
function App() {
  const { addNotification } = useContext(NotificationContext);
  
  return (
    <button onClick={() => addNotification('Operation successful!', 'success')}>
      Show Notification
    </button>
  );
}

Когда НЕ использовать порталы

  1. Элементы, которые наследуют стили родителя - CSS каскад не работает через портал

  2. Простые компоненты внутри дерева - нет смысла усложнять

  3. Когда нужно управлять z-index явно - лучше использовать CSS и Stacking Context

Альтернативы

Для простых наложений можно обойтись без порталов, используя position: fixed в CSS. Но порталы дают большей контроля над DOM структурой.

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