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 с глобальными состояниями, сохраняя предсказуемость поведения и упрощая тестирование компонентов.
on*Change для управления
состоянием через React-хуки или внешние хранилища.Управление состоянием в Radix UI обеспечивает гибкость, предсказуемость и доступность компонентов, позволяя строить сложные интерфейсы с минимальными усилиями и высокой интеграцией с внешними системами управления состоянием.