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

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

Контролируемые и неконтролируемые компоненты

Radix UI использует две модели управления состоянием:

  1. Неконтролируемые компоненты — компонент сам хранит внутреннее состояние. Разработчик получает уведомления о событиях через callback-функции, например onValueChange. Пример использования:
import * as Toggle from '@radix-ui/react-toggle';

<Toggle.Root defaultPressed={false} onPressedCha nge={(pressed) => console.log(pressed)}>
  Toggle Me
</Toggle.Root>

Здесь defaultPressed задает начальное состояние, а onPressedChange позволяет реагировать на изменения.

  1. Контролируемые компоненты — состояние полностью управляется извне через пропсы value или checked. В этом случае компонент не хранит внутреннее состояние, а лишь визуально отражает переданное значение:
import * as Toggle from '@radix-ui/react-toggle';
import { useState } from 'react';

function ControlledToggle() {
  const [pressed, setPressed] = useState(false);

  return (
    <Toggle.Root pressed={pressed} onPressedCha nge={setPressed}>
      Toggle Me
    </Toggle.Root>
  );
}

Контролируемые компоненты позволяют интегрировать Radix UI с глобальным состоянием приложения, например Redux или Zustand.

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

Для таких компонентов, как Accordion, Tabs и CheckboxGroup, Radix UI поддерживает множественное и единичное состояние.

  • Accordion: можно выбрать один или несколько элементов одновременно, используя пропсы type="single" или type="multiple" и управлять через value и onValueChange:
import * as Accordion from '@radix-ui/react-accordion';
import { useState } from 'react';

function ControlledAccordion() {
  const [openItems, setOpenItems] = useState(['item1']);

  return (
    <Accordion.Root type="multiple" value={openItems} onValueCha nge={setOpenItems}>
      <Accordion.Item value="item1">
        <Accordion.Header>Item 1</Accordion.Header>
        <Accordion.Content>Content 1</Accordion.Content>
      </Accordion.Item>
      <Accordion.Item value="item2">
        <Accordion.Header>Item 2</Accordion.Header>
        <Accordion.Content>Content 2</Accordion.Content>
      </Accordion.Item>
    </Accordion.Root>
  );
}
  • CheckboxGroup: поддерживает множественный выбор, управляемый массивом значений:
import * as Checkbox from '@radix-ui/react-checkbox';
import { useState } from 'react';

function CheckboxGroupControlled() {
  const [selected, setSelected] = useState(['apple']);

  return (
    <Checkbox.Group value={selected} onValueCha nge={setSelected}>
      <Checkbox value="apple">Apple</Checkbox>
      <Checkbox value="orange">Orange</Checkbox>
    </Checkbox.Group>
  );
}

Двусторонняя синхронизация состояния

Radix UI использует паттерн controlled component с callback-функциями, что обеспечивает двустороннюю синхронизацию состояния:

  • onValueChange или onPressedChange вызываются при любом изменении состояния.
  • Внешнее состояние обновляется через хук или глобальный стор.
  • Компонент получает новое состояние через пропс value или pressed, обеспечивая согласованность между UI и данными.

Особенности работы с состоянием при анимациях

Для компонентов с анимацией (например, Dialog или Popover) важно различать внешнее состояние открытия (open) и внутреннее состояние анимации (open + motion). Radix UI автоматически управляет фазами анимации через свои элементы, но внешнее состояние должно синхронизироваться с callback onOpenChange:

import * as Dialog from '@radix-ui/react-dialog';
import { useState } from 'react';

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

  return (
    <Dialog.Root open={open} onOpenCha nge={setOpen}>
      <Dialog.Trigger>Open Dialog</Dialog.Trigger>
      <Dialog.Content>
        <Dialog.Close>Close</Dialog.Close>
      </Dialog.Content>
    </Dialog.Root>
  );
}

Состояние с использованием кастомных хукoв

Для сложного управления состоянием, когда несколько компонентов должны реагировать на одни и те же события, удобно использовать кастомные хуки:

import { useState } from 'react';
import * as Tabs from '@radix-ui/react-tabs';

function useTabsState(initialValue) {
  const [value, setValue] = useState(initialValue);
  const selectTab = (tabValue) => setValue(tabValue);
  return { value, selectTab };
}

function TabsExample() {
  const tabs = useTabsState('tab1');

  return (
    <Tabs.Root value={tabs.value} onValueCha nge={tabs.selectTab}>
      <Tabs.List>
        <Tabs.Trigger value="tab1">Tab 1</Tabs.Trigger>
        <Tabs.Trigger value="tab2">Tab 2</Tabs.Trigger>
      </Tabs.List>
      <Tabs.Content value="tab1">Content 1</Tabs.Content>
      <Tabs.Content value="tab2">Content 2</Tabs.Content>
    </Tabs.Root>
  );
}

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

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

  • Использовать контролируемые компоненты для интеграции с глобальным состоянием.
  • Для простых элементов, не требующих синхронизации, достаточно неконтролируемых.
  • При работе с анимациями всегда учитывать фазу открытия/закрытия.
  • Для групп компонентов применять единое состояние, что снижает вероятность рассинхронизации UI.
  • Выделять логику управления в отдельные хуки, чтобы код оставался чистым и масштабируемым.

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