Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Используешь ли порталы
Да, использую. 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')
);
}
Где используются порталы
- Модальные окна - нужно разместить поверх всего контента, независимо от 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
);
}
- Тосты и уведомления - независимо от родителя:
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>
);
}
- Дропдауны и меню - нужно выходить за рамки 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>
Преимущества использования порталов
-
Избегаешь z-index проблем - модалка всегда на верху, если рендерится в body
-
Избегаешь overflow hidden - дропдаун не обрезается родителем
-
Чистая архитектура - логика модалки отделена от контента
-
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>
);
}
Когда НЕ использовать порталы
-
Элементы, которые наследуют стили родителя - CSS каскад не работает через портал
-
Простые компоненты внутри дерева - нет смысла усложнять
-
Когда нужно управлять z-index явно - лучше использовать CSS и Stacking Context
Альтернативы
Для простых наложений можно обойтись без порталов, используя position: fixed в CSS. Но порталы дают большей контроля над DOM структурой.
Вывод: Порталы - это не обязательный инструмент, но они значительно упрощают работу с модальными окнами, тостами и другими наложениями. Правильное использование порталов делает код более чистым и надёжным.