В реальных проектах редко существует возможность полностью переписать интерфейс на новую библиотеку за один этап. Чаще всего используется постепенная миграция, при которой новая технология внедряется частями без остановки разработки и без масштабного рефакторинга всего кода.
В контексте интерфейсов на React библиотека Radix UI хорошо подходит для такого сценария, поскольку её компоненты:
Это позволяет внедрять Radix UI поверх существующего интерфейса, не переписывая всё приложение сразу.
Постепенная миграция обычно проходит через несколько этапов:
Чаще всего миграция происходит при наличии следующих проблем:
1. Ограничения старой UI-библиотеки
Многие библиотеки предоставляют готовые компоненты со встроенной логикой и стилями. Это приводит к проблемам:
Radix UI предоставляет headless-компоненты, которые решают эти ограничения.
2. Проблемы доступности
Radix UI реализует:
Это избавляет от необходимости писать сложную логику вручную.
3. Необходимость гибкой дизайн-системы
Radix UI не содержит CSS, поэтому идеально подходит для создания:
Первый этап — установка Radix UI и интеграция в проект.
Radix распространяется в виде набора независимых пакетов.
npm install @radix-ui/react-dialog
npm install @radix-ui/react-dropdown-menu
npm install @radix-ui/react-tooltip
Каждый пакет устанавливается отдельно, что снижает размер итогового бандла.
Компоненты Radix состоят из нескольких примитивов.
Пример:
import * as Dialog from "@radix-ui/react-dialog";
Использование:
<Dialog.Root>
<Dialog.Trigger>Открыть</Dialog.Trigger>
<Dialog.Portal>
<Dialog.Overlay />
<Dialog.Content>
<Dialog.Title>Заголовок</Dialog.Title>
<Dialog.Description>
Описание окна
</Dialog.Description>
<Dialog.Close>Закрыть</Dialog.Close>
</Dialog.Content>
</Dialog.Portal>
</Dialog.Root>
Такой подход обеспечивает максимальную гибкость структуры DOM.
Главное преимущество Radix — возможность использовать его внутри уже существующих компонентов.
Предположим, что в проекте уже есть собственный компонент модального окна:
<Modal open={open} onCl ose={handleClose}>
<Content />
</Modal>
Постепенная миграция может выглядеть следующим образом.
function Modal({ open, onClose, children }) {
return (
<Dialog.Root open={open} onOpenCha nge={onClose}>
<Dialog.Portal>
<Dialog.Overlay className="overlay" />
<Dialog.Content className="content">
{children}
</Dialog.Content>
</Dialog.Portal>
</Dialog.Root>
);
}
Публичный API компонента не изменяется, но внутри используется Radix.
Такой подход позволяет:
Во время миграции удобно создавать адаптеры, которые повторяют API старых компонентов.
Пример старого dropdown-компонента:
<Dropdown
trigger={<Button />}
items={items}
/>
Адаптер на основе Radix:
import * as DropdownMenu from "@radix-ui/react-dropdown-menu";
function Dropdown({ trigger, items }) {
return (
<DropdownMenu.Root>
<DropdownMenu.Trigger asChild>
{trigger}
</DropdownMenu.Trigger>
<DropdownMenu.Content className="menu">
{items.map(item => (
<DropdownMenu.Item
key={item.id}
onSel ect={item.onClick}
>
{item.label}
</DropdownMenu.Item>
))}
</DropdownMenu.Content>
</DropdownMenu.Root>
);
}
Преимущества:
Обычно миграция начинается с небольших компонентов, которые имеют сложную логику:
Они хорошо подходят для первой интеграции.
Установка:
npm install @radix-ui/react-tooltip
Использование:
import * as Tooltip from "@radix-ui/react-tooltip";
<Tooltip.Provider>
<Tooltip.Root>
<Tooltip.Trigger>
Кнопка
</Tooltip.Trigger>
<Tooltip.Content className="tooltip">
Подсказка
</Tooltip.Content>
</Tooltip.Root>
</Tooltip.Provider>
В большинстве случаев такой компонент можно внедрить без изменения существующего интерфейса.
asChildОдной из ключевых возможностей Radix является свойство
asChild.
Оно позволяет использовать существующий компонент вместо стандартного элемента.
Пример:
<Dialog.Trigger asChild>
<Button>Открыть</Button>
</Dialog.Trigger>
Без asChild:
<button>
<Button />
</button>
С asChild:
<Button />
Это особенно важно при миграции, поскольку позволяет:
После внедрения базовых примитивов переходят к более сложным компонентам:
Такие элементы часто содержат сложную логику:
Radix UI уже реализует эту функциональность.
import * as Dialog from "@radix-ui/react-dialog";
<Dialog.Root>
<Dialog.Trigger>
Открыть окно
</Dialog.Trigger>
<Dialog.Portal>
<Dialog.Overlay className="overlay" />
<Dialog.Content className="dialog">
<Dialog.Title>
Настройки
</Dialog.Title>
<Dialog.Close>
Закрыть
</Dialog.Close>
</Dialog.Content>
</Dialog.Portal>
</Dialog.Root>
После внедрения нескольких компонентов обычно создаётся обёрточный слой.
Структура проекта:
components/
ui/
button
dialog
dropdown
tooltip
Пример компонента:
import * as Dialog from "@radix-ui/react-dialog";
export function Modal({ children, open, onOpenChange }) {
return (
<Dialog.Root open={open} onOpenCha nge={onOpenChange}>
<Dialog.Portal>
<Dialog.Overlay className="overlay"/>
<Dialog.Content className="content">
{children}
</Dialog.Content>
</Dialog.Portal>
</Dialog.Root>
);
}
Теперь Radix становится внутренней реализацией, а не публичным API.
Radix UI может использоваться вместе с:
Пример с Tailwind:
<Dialog.Content className="
bg-white
p-6
rounded-lg
shadow-xl
">
Контент
</Dialog.Content>
Поскольку Radix не содержит CSS, конфликтов стилей практически не возникает.
После миграции нескольких компонентов рекомендуется выделить отдельный слой:
src
├ ui
│ ├ primitives
│ ├ components
│ └ hooks
Где:
primitives
обёртки над Radix.
components
компоненты дизайн-системы.
hooks
дополнительная логика.
Такой подход:
Финальный этап миграции — постепенное удаление старых компонентов.
Чаще всего заменяются:
После этого кодовая база:
Распространённая последовательность внедрения:
Такая стратегия позволяет минимизировать риски и постепенно внедрять новую архитектуру.
Минимальные риски
Изменения происходят поэтапно.
Сохранение стабильности
Основной интерфейс продолжает работать.
Гибкость
Можно остановить миграцию на любом этапе.
Отсутствие больших рефакторингов
Radix внедряется поверх существующего кода.
При использовании Radix UI в процессе миграции рекомендуется:
asChild для интеграции со старыми
компонентамиТакая архитектура позволяет превратить Radix UI в фундамент новой компонентной системы, не нарушая стабильность существующего интерфейса.