Сталкивались когда-нибудь с проблемой, когда нужно рендерить элемент за пределами текущей DOM-иерархии?
Например, модальные окна, которые не должны быть вложены в основное дерево из-за проблем с позиционированием или всплывающие подсказки, которые всегда должны быть на переднем плане
Для таких задач React предлагает решение — порталы
В документации реакта приведён такой код:
jsx1import { createPortal } from 'react-dom'; 2 3<div> 4 <p>Текст расположен в родительском диве</p> 5 {createPortal( 6 <p>Текст расположен в document.body</p>, 7 document.body 8 )} 9</div> 10
Работает этот код проще простого: элемент, переданный в функцию createPortal, будет маунтиться реактом не в родительский див, а в document.body. Работать это будет с деревом любой вложенности
Такой код будет работать, но он не очень удобен, поэтому многие компонентные библиотеки максимально упрощают порталы для разработчиков и делают подобные компоненты — они есть в Chakra UI, Material UI, Semantic UI и других либах
Но, на самом деле, там нет ничего сложного
Если максимально упростить, то можно прийти к такому варианту:
jsx1const Portal = ({ children }: PropsWithChildren) => { 2 const [container] = useState(() => document.createElement('div')); 3 4 useLayoutEffect(() => { 5 document.body.appendChild(container); 6 return () => { 7 document.body.removeChild(container); 8 }; 9 }, [container]); 10 11 return createPortal(children, container); 12} 13 14// ... 15 16<Portal> 17 <p>Текст внутри портала</p> 18</Portal> 19
Тут стоит уточнить две детали:
- Мы создаём новый div внутри useState, чтобы проще было контролировать портал Если мы будем рендерить контент сразу в document.body, то можно словить много проблем со стилями и отслеживанием самого портала
Используем мы именно useState, чтобы создавать элемент единожды и гарантировано на первый рендер компонента. Элемент создается внутри колбека инициализации состояния — он всегда вызывается единожды на маунт компонента
Как альтернатива, можно обойтись и рефом
- В useLayoutEffect мы привязываем жизненный цикл тега-обёртки к циклу компонента портала Тоже полезно, чтобы лишний раз не задумываться о том, как живёт портал и не создавать ненужных элементов в вёрстке
useLayoutEffect используется вместо useEffect, чтобы обрабатывать портал без лишних мерцаний и более плавно
Вообще, тема разных эффектов и где какой использовать — это отдельная крупная тема, возможно сделаю об этом пост в будущем
Да и всё. Тут компонент на 10 строчек буквально, ничего сверхъестественного. Если вам нужны порталы, то задумайтесь — скорее всего вам хватит такой простой реализации
Статья была полезной?
Читайте также:
— 2 минуты
Как реагировать на изменения объекта
В JavaScript обычные объекты не умеют уведомлять о своих изменениях, однако...
— 2 минуты
Что такое Server-Sent Events
SSE — это технология для однонаправленного соединения между сервером и клие...
— 4 минуты
Связываем React и localStorage через useSyncExternalStore
Как согласовать изменение состояния в реакте и поля в localStorage? До нед...