Телепортация в React.js
Знаете ли вы, что React предоставляет специальный и очень мощный компонент под названием «Портал», который может монтировать своих дочерних элементов в совершенно другом месте внутри вашего приложения? Если нет, то пора узнать что-то новое!
Анатомия портала React.js
Вот краткий обзор того, как компонент Portal в React.js работал раньше.
- Где-то в вашем коде вы монтируете элемент, в котором будут размещены дочерние элементы портала.
- Тогда вы получите ссылку на этот элемент
- Затем в другом месте вашего приложения вы монтируете компонент Portal и передаете ссылку, полученную из host-element.
- Все, что теперь смонтировано внутри портала, будет фактически смонтировано на хосте с первого шага.
Чтобы лучше понять, что происходит, я создал следующие простые визуализации, иллюстрирующие функциональность портала.
На основе этих изображений на следующих рисунках показан тот же процесс, но на этот раз с упрощенными примерами кода.
Портал даже мощнее, чем вы думаете вначале. Он даже позволяет отображать несколько дочерних элементов из нескольких порталов в один и тот же целевой элемент. Порядок установки Порталов также определяет порядок элементов в хосте.
Поделиться ссылкой на элемент-хост, который будет содержать всех телепортированных потомков, также очень просто. Официальная документация описывает использование document.getElementById, но вы также можете использовать useRef-hook из React.js. В следующем примере кода демонстрируется простая реализация, которая показывает все, что было описано до сих пор.
import React, { useEffect, useRef, useState } from "react";
import ReactDOM, { createPortal } from "react-dom";
//
// Link to sandbox:
// 👉 https://codesandbox.io/s/react-16-8-0-forked-d8lm1?file=/src/index.js:0-1067
//
// A simple demonstration of the React-Portal
// w/ multiple portals for the same target.
function App() {
const ref = useRef(null);
const [isRefReady, setIsRefReady] = useState(false);
// Run one more render to actually
// show the content of our portal.
// This is necessary, as the setter
// for the 'ref' alone won't trigger
// a render.
useEffect(() => {
setIsRefReady(Boolean(ref));
}, []);
return (
<div>
<div>
{/* The 'target' aka host for our portals. */}
<header ref={ref} style={{display: "flex", gap: 8 }} />
<hr/>
</div>
<h1>
Debug
</h1>
{isRefReady && createPortal(<button>one</button>, ref.current)}
{isRefReady && createPortal(<button>two</button>, ref.current)}
<div>
{isRefReady && createPortal(<button>three</button>, ref.current)}
</div>
</div>
);
}
ReactDOM.render(<App />, document.querySelector("#root"));
Как компонент Portal позволяет упростить кодирование
Вероятно, наиболее распространенным вариантом использования портала React.js является реализация модального компонента. Вы можете просто определить цель в корне дерева вашего приложения и использовать компонент Portal для монтирования модального окна отовсюду, надежно плавающего над всем содержимым.
Другой вариант использования, который я реализовал, - это использование контейнера вкладок. Поскольку для контейнера вкладок требуется как сам элемент вкладки, так и контент для визуализации при выборе вкладки, я просто определяю два хоста: один для строки вкладок, а другой - для выбранного содержимого.
Затем я просто монтирую массив компонентов, каждый из которых использует портал для элемента вкладки, а также для выбранного содержимого. Это позволяет мне разместить бизнес-логику там, где она действительно должна быть - в каждом отдельном компоненте.
Конечно, есть еще более интересные варианты использования Portal-компонента. Самым большим преимуществом, которое, как мне кажется, он дает, является тесная связь бизнес-логики в одном компоненте, дочерние элементы которого затем монтируются в разных местах. Этот шаблон обеспечивает большую гибкость и настройку, но может привести к путанице в вашей кодовой базе, как на самом деле связаны представления.
Также ознакомьтесь с официальной документацией, чтобы узнать все подробности о порталах React.js.