Организация кода

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

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

Основные задачи организации кода:

  • изоляция логики интерфейса;
  • повторное использование компонентов;
  • управление состоянием и композициями;
  • разделение ответственности между слоями приложения;
  • упрощение масштабирования проекта.

Грамотно организованная структура позволяет использовать преимущества Radix UI — модульность, доступность и контроль над поведением компонентов.


Архитектура компонентов

Radix UI строится вокруг концепции компонентов-примитивов (primitives). Каждый интерфейсный элемент состоит из нескольких независимых частей.

Например, компонент Dialog включает:

  • Dialog.Root
  • Dialog.Trigger
  • Dialog.Portal
  • Dialog.Overlay
  • Dialog.Content
  • Dialog.Title
  • Dialog.Description
  • Dialog.Close

Каждая часть выполняет конкретную роль. Такая архитектура требует аккуратной композиции.

Пример базовой структуры:

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

function UserDialog() {
  return (
    <Dialog.Root>
      <Dialog.Trigger>Открыть</Dialog.Trigger>

      <Dialog.Portal>
        <Dialog.Overlay className="overlay" />

        <Dialog.Content className="content">
          <Dialog.Title>Профиль пользователя</Dialog.Title>
          <Dialog.Description>
            Информация о текущем пользователе
          </Dialog.Description>

          <Dialog.Close>Закрыть</Dialog.Close>
        </Dialog.Content>
      </Dialog.Portal>
    </Dialog.Root>
  );
}

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


Разделение компонентов по уровням

В проектах с Radix UI удобно использовать многоуровневую структуру компонентов.

1. Примитивные компоненты

Компоненты, непосредственно использующие Radix UI.

components/primitives

Пример:

components/primitives/Dialog
components/primitives/Popover
components/primitives/DropdownMenu

Эти компоненты обычно:

  • инкапсулируют базовую конфигурацию Radix;
  • задают стили;
  • определяют структуру.

Пример:

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

export function Modal({ children, title }) {
  return (
    <Dialog.Root>
      <Dialog.Trigger className="button">
        Открыть
      </Dialog.Trigger>

      <Dialog.Portal>
        <Dialog.Overlay className="overlay" />

        <Dialog.Content className="modal">
          <Dialog.Title>{title}</Dialog.Title>

          {children}

          <Dialog.Close className="close">
            Закрыть
          </Dialog.Close>
        </Dialog.Content>
      </Dialog.Portal>
    </Dialog.Root>
  );
}

2. UI-компоненты

Компоненты пользовательского интерфейса, построенные на основе примитивов.

components/ui

Пример:

UserProfileModal
SettingsPopover
NotificationsMenu
import { Modal } from "../primitives/Modal";

export function UserProfileModal({ user }) {
  return (
    <Modal title="Профиль">
      <div className="profile">
        <p>{user.name}</p>
        <p>{user.email}</p>
      </div>
    </Modal>
  );
}

3. Бизнес-компоненты

Компоненты, содержащие бизнес-логику.

features

Пример:

features/auth
features/dashboard
features/profile
import { UserProfileModal } from "@/components/ui/UserProfileModal";

export function ProfileSection({ user }) {
  return (
    <section>
      <h2>Профиль</h2>
      <UserProfileModal user={user} />
    </section>
  );
}

Такой подход предотвращает смешивание интерфейсной логики и бизнес-логики.


Структура каталогов

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

src
 ├── components
 │    ├── primitives
 │    │     ├── Dialog
 │    │     ├── Popover
 │    │     ├── Tooltip
 │    │
 │    ├── ui
 │    │     ├── UserMenu
 │    │     ├── Modal
 │    │     ├── NotificationList
 │
 ├── features
 │    ├── auth
 │    ├── settings
 │    ├── profile
 │
 ├── hooks
 │
 ├── utils
 │
 └── styles

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

  • изоляция UI-слоя;
  • повторное использование примитивов;
  • удобная навигация по проекту.

Инкапсуляция Radix компонентов

Одна из распространённых практик — скрывать прямое использование Radix UI внутри собственного слоя компонентов.

Это позволяет:

  • централизованно управлять API;
  • изменять библиотеку без переписывания всего проекта;
  • стандартизировать стили.

Пример обёртки для Tooltip.

import * as Tooltip from "@radix-ui/react-tooltip";

export function AppTooltip({ content, children }) {
  return (
    <Tooltip.Provider>
      <Tooltip.Root>

        <Tooltip.Trigger asChild>
          {children}
        </Tooltip.Trigger>

        <Tooltip.Portal>
          <Tooltip.Content className="tooltip">
            {content}
          </Tooltip.Content>
        </Tooltip.Portal>

      </Tooltip.Root>
    </Tooltip.Provider>
  );
}

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

<AppTooltip content="Удалить">
  <button>?</button>
</AppTooltip>

Использование композиции

Radix UI активно использует композиционный подход.

Компоненты не навязывают структуру, а позволяют создавать её вручную.

Пример композиции выпадающего меню:

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

export function UserMenu() {
  return (
    <DropdownMenu.Root>

      <DropdownMenu.Trigger className="trigger">
        Меню
      </DropdownMenu.Trigger>

      <DropdownMenu.Content className="menu">

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

        <DropdownMenu.Item>
          Настройки
        </DropdownMenu.Item>

        <DropdownMenu.Separator />

        <DropdownMenu.Item>
          Выход
        </DropdownMenu.Item>

      </DropdownMenu.Content>

    </DropdownMenu.Root>
  );
}

Чтобы избежать дублирования, композиции часто разбиваются на более мелкие компоненты.


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

Часто встречающиеся структуры стоит оформлять в отдельные компоненты.

Пример — стандартный Dropdown Menu Item.

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

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

<MenuItem onSel ect={logout}>
  Выйти
</MenuItem>

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

  • единый стиль интерфейса;
  • минимизацию повторяющегося кода;
  • централизованное управление поведением.

Управление состоянием

Radix UI поддерживает контролируемый и неконтролируемый режимы.

Неконтролируемый режим

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

<Dialog.Root>

Это самый простой вариант.


Контролируемый режим

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

const [open, setOpen] = useState(false);

<Dialog.Root open={open} onOpenCha nge={setOpen}>

Контролируемый режим полезен:

  • при синхронизации состояния;
  • при интеграции с глобальными хранилищами;
  • при сложной логике интерфейса.

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

Повторяющуюся логику удобно выносить в хуки.

Пример:

import { useState } from "react";

export function useModal() {
  const [open, setOpen] = useState(false);

  function openModal() {
    setOpen(true);
  }

  function closeModal() {
    setOpen(false);
  }

  return {
    open,
    openModal,
    closeModal,
    setOpen
  };
}

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

const modal = useModal();

<Dialog.Root
  open={modal.open}
  onOpenCha nge={modal.setOpen}
>

Стандартизация стилизации

Radix UI не предоставляет встроенных стилей. Поэтому стили должны быть организованы отдельно.

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

  • CSS Modules
  • Tailwind
  • Styled Components
  • Vanilla Extract

Пример с CSS Modules:

Dialog.module.css
.overlay {
  background: rgba(0,0,0,0.5);
}

.content {
  background: white;
  border-radius: 8px;
}

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

import styles from "./Dialog.module.css";

<Dialog.Content className={styles.content}>

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

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

<Tooltip.Trigger asChild>
  <button>Кнопка</button>
</Tooltip.Trigger>

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

С asChild используется переданный элемент.

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

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

Управление Portal

Многие компоненты Radix используют Portal для рендеринга вне текущего DOM-дерева.

Пример:

<Dialog.Portal>

Это необходимо для:

  • модальных окон;
  • всплывающих меню;
  • tooltip;
  • popover.

Portal предотвращает проблемы:

  • с overflow
  • с z-index
  • с вложенностью компонентов.

Оптимизация производительности

Несколько практик помогают поддерживать высокую производительность:

1. Ленивая загрузка компонентов

const SettingsModal = lazy(() => import("./SettingsModal"));

2. Разделение больших компонентов

Интерфейсные блоки должны быть маленькими и специализированными.


3. Мемоизация

const MemoMenu = React.memo(UserMenu);

4. Вынос логики

Логика не должна находиться внутри сложных JSX-структур.


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

Некоторые компоненты Radix используют Context API.

Для сложных интерфейсов можно создавать собственные контексты.

Пример:

const ModalContext = createContext(null);
export function ModalProvider({ children }) {
  const modal = useModal();

  return (
    <ModalContext.Provider value={modal}>
      {children}
    </ModalContext.Provider>
  );
}

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


Подход “Headless UI”

Radix UI относится к категории headless библиотек.

Особенности:

  • отсутствуют стили;
  • предоставляется только логика и доступность;
  • интерфейс полностью контролируется разработчиком.

Организация кода должна учитывать этот принцип:

  • стили — отдельно;
  • логика — отдельно;
  • структура — через композицию компонентов.

Такой подход делает интерфейс гибким и независимым от конкретной дизайн-системы.