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

Radix UI предоставляет набор низкоуровневых компонентов для создания доступного и управляемого пользовательского интерфейса. Важнейшей частью работы с такими компонентами является управление их состоянием. Radix UI строит свои компоненты вокруг концепции контролируемого и неконтролируемого состояния, что позволяет гибко интегрировать их с любыми системами управления состоянием, будь то React-хуки, Redux или Zustand.

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

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>Открыть диалог</Dialog.Trigger>
      <Dialog.Content>
        <Dialog.Title>Пример контролируемого диалога</Dialog.Title>
        <Dialog.Description>Состояние открыто/закрыто полностью управляется извне</Dialog.Description>
        <Dialog.Close>Закрыть</Dialog.Close>
      </Dialog.Content>
    </Dialog.Root>
  );
}

В этом примере Dialog.Root принимает open и onOpenChange, что делает диалог полностью контролируемым. Любое изменение состояния инициируется через setOpen, что обеспечивает синхронизацию состояния компонента с внешними данными.


Неконтролируемое состояние

Неконтролируемое состояние позволяет компоненту самому управлять своим внутренним состоянием. Это удобно, когда нет необходимости синхронизировать состояние с внешними источниками.

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

function UncontrolledDialog() {
  return (
    <Dialog.Root defaultOpen={false}>
      <Dialog.Trigger>Открыть диалог</Dialog.Trigger>
      <Dialog.Content>
        <Dialog.Title>Неконтролируемый диалог</Dialog.Title>
        <Dialog.Description>Компонент сам управляет открытием и закрытием</Dialog.Description>
        <Dialog.Close>Закрыть</Dialog.Close>
      </Dialog.Content>
    </Dialog.Root>
  );
}

Использование defaultOpen задаёт начальное состояние, после чего компонент самостоятельно управляет открытием и закрытием диалога. При этом внешний код не контролирует эти изменения.


Синхронизация с внешними состояниями

Radix UI поддерживает гибридный подход, когда часть состояния контролируется извне, а часть — компонентом. Это достигается с помощью пропсов default* и *Change.

Пример комбинированного управления состоянием Accordion:

import * as Accordion from '@radix-ui/react-accordion';
import { useState } from 'react';

function HybridAccordion() {
  const [openItem, setOpenItem] = useState('item-1');

  return (
    <Accordion.Root type="single" value={openItem} onValueCha nge={setOpenItem}>
      <Accordion.Item value="item-1">
        <Accordion.Header>
          <Accordion.Trigger>Раздел 1</Accordion.Trigger>
        </Accordion.Header>
        <Accordion.Content>Содержимое раздела 1</Accordion.Content>
      </Accordion.Item>
      <Accordion.Item value="item-2">
        <Accordion.Header>
          <Accordion.Trigger>Раздел 2</Accordion.Trigger>
        </Accordion.Header>
        <Accordion.Content>Содержимое раздела 2</Accordion.Content>
      </Accordion.Item>
    </Accordion.Root>
  );
}

Здесь значение value и колбэк onValueChange позволяют синхронизировать выбранный элемент с внешним состоянием, при этом Radix UI управляет анимацией открытия и закрытия контента.


Управление фокусом и взаимодействиями

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

Пример с Tabs:

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

function ControlledTabs() {
  const [activeTab, setActiveTab] = useState('tab1');

  return (
    <Tabs.Root value={activeTab} onValueCha nge={setActiveTab}>
      <Tabs.List>
        <Tabs.Trigger value="tab1">Вкладка 1</Tabs.Trigger>
        <Tabs.Trigger value="tab2">Вкладка 2</Tabs.Trigger>
      </Tabs.List>
      <Tabs.Content value="tab1">Контент вкладки 1</Tabs.Content>
      <Tabs.Content value="tab2">Контент вкладки 2</Tabs.Content>
    </Tabs.Root>
  );
}

Использование value и onValueChange позволяет полностью контролировать активную вкладку через внешний стейт, что важно для интеграции с роутером или формами.


Интеграция с глобальными системами состояния

Radix UI легко интегрируется с глобальными стейт-менеджерами, такими как Redux, Zustand или Recoil. Компоненты полностью поддерживают контролируемый режим, поэтому можно подключать их к любому хранилищу состояния:

import { useSelector, useDispatch } from 'react-redux';
import * as Dialog from '@radix-ui/react-dialog';
import { toggleDialog } from './dialogSlice';

function ReduxDialog() {
  const open = useSelector(state => state.dialog.isOpen);
  const dispatch = useDispatch();

  return (
    <Dialog.Root open={open} onOpenCha nge={() => dispatch(toggleDialog())}>
      <Dialog.Trigger>Открыть диалог</Dialog.Trigger>
      <Dialog.Content>
        <Dialog.Title>Диалог с Redux</Dialog.Title>
        <Dialog.Close>Закрыть</Dialog.Close>
      </Dialog.Content>
    </Dialog.Root>
  );
}

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


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

  1. Контролируемое состояние следует использовать, когда требуется синхронизация с внешними данными или другими компонентами.
  2. Неконтролируемое состояние удобно для простых компонентов, где нет необходимости в глобальной синхронизации.
  3. Гибридный подход — оптимальный выбор для сложных интерфейсов, где часть состояния должна управляться извне, а часть — компонентом.
  4. Использовать колбэки on*Change для управления состоянием через React-хуки или внешние хранилища.
  5. Учитывать доступность: Radix UI автоматически обрабатывает фокус и события клавиатуры, что упрощает реализацию доступных интерфейсов.

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