Переиспользование компонентов

Radix UI построен вокруг идеи композиции и повторного использования интерфейсных примитивов. Библиотека не предоставляет готовые визуальные компоненты, а поставляет поведенческие блоки, которые можно многократно использовать в разных частях приложения.

Переиспользование достигается за счёт нескольких ключевых механизмов:

  • разделение логики и представления
  • композиция через compound components
  • механизм asChild
  • использование React Context
  • инкапсуляция поведения в хуках
  • создание обёрток (wrapper components)

Такая архитектура позволяет строить сложные интерфейсы из небольших универсальных элементов.


Поведенческие примитивы Radix UI

Компоненты Radix представляют собой headless primitives — элементы без встроенного дизайна. Они содержат:

  • управление состоянием
  • доступность (ARIA)
  • управление фокусом
  • обработку клавиатуры
  • взаимодействие с DOM

Пример структуры компонента:

import * as Dialog from "@radix-ui/react-dialog";

<Dialog.Root>
  <Dialog.Trigger>Открыть</Dialog.Trigger>
  <Dialog.Content>
    <Dialog.Title>Заголовок</Dialog.Title>
  </Dialog.Content>
</Dialog.Root>

Каждый элемент (Root, Trigger, Content) — самостоятельный компонент, который может быть переиспользован.


Паттерн Compound Components

Radix активно использует паттерн compound components — набор взаимосвязанных компонентов, которые работают внутри общего контекста.

Типичная структура:

Component.Root
Component.Trigger
Component.Content
Component.Item
Component.Label

Например:

import * as DropdownMenu from "@radix-ui/react-dropdown-menu";

<DropdownMenu.Root>
  <DropdownMenu.Trigger>Меню</DropdownMenu.Trigger>

  <DropdownMenu.Content>
    <DropdownMenu.Item>Профиль</DropdownMenu.Item>
    <DropdownMenu.Item>Настройки</DropdownMenu.Item>
  </DropdownMenu.Content>
</DropdownMenu.Root>

Каждый элемент можно оборачивать, стилизовать или расширять, сохраняя базовую логику.


Создание переиспользуемых обёрток

На практике компоненты Radix редко используются напрямую. Чаще создаются обёртки, инкапсулирующие стили и поведение.

Пример создания кнопки для триггера меню:

import * as DropdownMenu from "@radix-ui/react-dropdown-menu";

export function MenuButton({ children }) {
  return (
    <DropdownMenu.Trigger className="menu-button">
      {children}
    </DropdownMenu.Trigger>
  );
}

Теперь компонент можно использовать многократно:

<DropdownMenu.Root>
  <MenuButton>Открыть меню</MenuButton>

  <DropdownMenu.Content>
    <DropdownMenu.Item>Профиль</DropdownMenu.Item>
  </DropdownMenu.Content>
</DropdownMenu.Root>

Использование asChild

Одна из ключевых возможностей Radix — проп asChild. Он позволяет заменить DOM-элемент компонента Radix собственным компонентом.

Проблема без asChild

Без asChild компонент создаёт собственный DOM-элемент:

<Dialog.Trigger>
  <button>Открыть</button>
</Dialog.Trigger>

В DOM получается вложенная структура:

button
  button

Это нарушает семантику HTML.


Решение через asChild

<Dialog.Trigger asChild>
  <button className="button">
    Открыть
  </button>
</Dialog.Trigger>

Теперь Radix передаёт поведение дочернему элементу, не создавая лишнего DOM.

Преимущества:

  • чистая DOM-структура
  • полная кастомизация
  • повторное использование собственных компонентов

Переиспользование пользовательских компонентов

Компоненты интерфейса можно объединять с примитивами Radix.

Пример универсальной кнопки

function Button({ children, ...props }) {
  return (
    <button className="ui-button" {...props}>
      {children}
    </button>
  );
}

Использование с Radix:

<Dialog.Trigger asChild>
  <Button>Открыть диалог</Button>
</Dialog.Trigger>

Кнопка остаётся универсальным компонентом, а Radix добавляет поведение.


Создание абстракций интерфейса

Radix позволяет создавать более высокоуровневые компоненты, скрывающие внутреннюю структуру.

Пример: DropdownMenuWrapper

import * as DropdownMenu from "@radix-ui/react-dropdown-menu";

export function Menu({ trigger, children }) {
  return (
    <DropdownMenu.Root>
      <DropdownMenu.Trigger asChild>
        {trigger}
      </DropdownMenu.Trigger>

      <DropdownMenu.Content className="menu">
        {children}
      </DropdownMenu.Content>
    </DropdownMenu.Root>
  );
}

Использование:

<Menu trigger={<Button>Меню</Button>}>
  <DropdownMenu.Item>Профиль</DropdownMenu.Item>
  <DropdownMenu.Item>Выход</DropdownMenu.Item>
</Menu>

Такая абстракция уменьшает повторение кода.


Инкапсуляция логики через хуки

Логику компонентов можно выносить в кастомные хуки, что упрощает переиспользование.

Пример:

function useMenuState() {
  const [open, setOpen] = React.useState(false);

  return {
    open,
    onOpenChange: setOpen
  };
}

Использование:

function MenuComponent() {
  const menu = useMenuState();

  return (
    <DropdownMenu.Root {...menu}>
      ...
    </DropdownMenu.Root>
  );
}

Хук можно применять в разных компонентах.


Повторное использование структур интерфейса

Radix-компоненты позволяют создавать повторяемые UI-шаблоны.

Например, элементы меню.

Компонент MenuItem

function MenuItem({ children, ...props }) {
  return (
    <DropdownMenu.Item
      className="menu-item"
      {...props}
    >
      {children}
    </DropdownMenu.Item>
  );
}

Использование:

<MenuItem>Редактировать</MenuItem>
<MenuItem>Удалить</MenuItem>
<MenuItem>Копировать</MenuItem>

Преимущества:

  • единый стиль
  • централизованные изменения
  • уменьшение дублирования

Композиция компонентов

Radix поощряет композицию, при которой сложные компоненты собираются из простых.

Пример структуры меню

Menu
 ├── MenuTrigger
 ├── MenuContent
 │    ├── MenuItem
 │    ├── MenuItem
 │    └── MenuSeparator

Каждый элемент может быть:

  • переиспользован
  • расширен
  • стилизован

Переиспользование стилей

Поскольку Radix не содержит стилей, внешний вид компонентов определяется системой стилей приложения.

Популярные подходы:

  • CSS Modules
  • Tailwind
  • Styled Components
  • Vanilla Extract

Пример через CSS

.menu-item {
  padding: 8px 12px;
  cursor: pointer;
}

.menu-item:hover {
  background: #f3f3f3;
}

Это позволяет использовать один и тот же компонент во множестве интерфейсов.


Переиспользование через композицию Layout-компонентов

Можно создавать layout-компоненты, содержащие Radix внутри.

Пример: ModalLayout.

function ModalLayout({ title, children }) {
  return (
    <Dialog.Content className="modal">
      <Dialog.Title>{title}</Dialog.Title>

      <div className="modal-body">
        {children}
      </div>
    </Dialog.Content>
  );
}

Использование:

<Dialog.Root>
  <Dialog.Trigger asChild>
    <Button>Открыть</Button>
  </Dialog.Trigger>

  <ModalLayout title="Настройки">
    <SettingsForm />
  </ModalLayout>
</Dialog.Root>

Переиспользование через slot-композицию

Radix позволяет создавать компоненты с слотами.

Пример:

function Card({ header, footer, children }) {
  return (
    <div className="card">
      <div className="card-header">{header}</div>

      <div className="card-body">
        {children}
      </div>

      <div className="card-footer">
        {footer}
      </div>
    </div>
  );
}

Использование:

<Card
  header="Заголовок"
  footer={<Button>Сохранить</Button>}
>
  Контент карточки
</Card>

Такая архитектура облегчает повторное использование.


Переиспользование с использованием Context

Radix активно использует React Context для передачи состояния между частями компонента.

Это позволяет компонентам:

  • взаимодействовать без prop drilling
  • использоваться независимо
  • быть переставляемыми в DOM

Например, DropdownMenu.Item автоматически получает контекст меню.

Благодаря этому элементы можно свободно комбинировать.


Стратегии построения библиотеки компонентов

При разработке системы UI на основе Radix часто применяется трёхуровневая архитектура.

Уровень 1 — Radix primitives

Низкоуровневые компоненты:

Dialog
Popover
DropdownMenu
Tabs
Tooltip

Уровень 2 — UI-обёртки

Компоненты со стилями:

Modal
Menu
Tabs
Tooltip
Select

Уровень 3 — бизнес-компоненты

Компоненты приложения:

UserMenu
SettingsModal
ProductTabs
ProfileDropdown

Такой подход обеспечивает:

  • максимальное повторное использование
  • слабую связанность
  • масштабируемость интерфейса

Организация файлов переиспользуемых компонентов

Типичная структура проекта:

components
  ui
    button
      Button.jsx
    dialog
      Modal.jsx
    menu
      Menu.jsx

  shared
    MenuItem.jsx
    FormField.jsx

Radix используется внутри ui-слоя, а бизнес-компоненты строятся поверх него.


Преимущества архитектуры переиспользования

Использование Radix UI для построения переиспользуемых компонентов даёт ряд преимуществ:

Предсказуемость интерфейсов

Все компоненты используют одинаковые примитивы.

Доступность

ARIA-поведение уже реализовано.

Минимизация дублирования

Повторное использование снижает объём кода.

Масштабируемость

Компоненты легко расширяются и комбинируются.

Гибкость

Отсутствие встроенных стилей позволяет адаптировать компоненты под любую дизайн-систему.


Типичные ошибки при переиспользовании

Чрезмерная абстракция

Слишком универсальные компоненты становятся сложными.


Жёсткая связность компонентов

Компоненты не должны зависеть от конкретного контекста приложения.


Нарушение композиции

Лучше комбинировать маленькие компоненты, чем создавать один большой.


Практика построения переиспользуемых интерфейсов

Эффективное использование Radix UI строится на нескольких принципах:

  • выделение универсальных UI-компонентов
  • создание тонких обёрток над Radix
  • использование asChild для интеграции собственных компонентов
  • разделение поведения и представления
  • применение композиции вместо наследования

Такая архитектура позволяет строить крупные интерфейсы из небольших, независимых и легко переиспользуемых компонентов.