Порталы в React

  —  3 минуты

#web#javascript#theory#code
Читать статью в Telegram

Сталкивались когда-нибудь с проблемой, когда нужно рендерить элемент за пределами текущей DOM-иерархии?

Например, модальные окна, которые не должны быть вложены в основное дерево из-за проблем с позиционированием или всплывающие подсказки, которые всегда должны быть на переднем плане

Для таких задач React предлагает решение — порталы

В документации реакта приведён такой код:

jsx
1import { 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 и других либах

Но, на самом деле, там нет ничего сложного

Если максимально упростить, то можно прийти к такому варианту:

jsx
1const 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

Тут стоит уточнить две детали:

  1. Мы создаём новый div внутри useState, чтобы проще было контролировать портал Если мы будем рендерить контент сразу в document.body, то можно словить много проблем со стилями и отслеживанием самого портала

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

Как альтернатива, можно обойтись и рефом

  1. В useLayoutEffect мы привязываем жизненный цикл тега-обёртки к циклу компонента портала Тоже полезно, чтобы лишний раз не задумываться о том, как живёт портал и не создавать ненужных элементов в вёрстке

useLayoutEffect используется вместо useEffect, чтобы обрабатывать портал без лишних мерцаний и более плавно

Вообще, тема разных эффектов и где какой использовать — это отдельная крупная тема, возможно сделаю об этом пост в будущем

Да и всё. Тут компонент на 10 строчек буквально, ничего сверхъестественного. Если вам нужны порталы, то задумайтесь — скорее всего вам хватит такой простой реализации

Статья была полезной?