Взаимодействие между хуками

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


Хуки состояния и хуки поведения

React Aria разделяет состояние компонента и поведение компонента на разные уровни:

  1. Хуки состояния (useToggleState, useComboBoxState, useListState) отвечают за хранение и обновление состояния компонента. Они не знают о DOM и не взаимодействуют с событиями напрямую.
  2. Хуки поведения (useButton, useCheckbox, useComboBox) предоставляют обработчики событий, атрибуты ARIA и привязки к DOM. Они используют состояние из хуков состояния и преобразуют его в доступное поведение.

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


Передача состояния между хуками

Хуки поведения обычно принимают объект состояния как аргумент:

import { useButton } from "@react-aria/button";
import { useToggleState } from "@react-stately/toggle";

function ToggleButton(props) {
  const state = useToggleState(props);
  const { buttonProps } = useButton(props, state);

  return <button {...buttonProps}>{state.isSelected ? "Вкл" : "Выкл"}</button>;
}
  • useToggleState управляет булевым состоянием isSelected.
  • useButton использует это состояние для назначения правильных атрибутов ARIA (aria-pressed) и обработчиков событий (onClick).

Ключевой момент: хуки поведения не изменяют состояние напрямую; они лишь уведомляют состояние о действиях пользователя.


Комбинирование нескольких хуков

В сложных компонентах часто требуется объединение нескольких хуков поведения. Например, комбинированный компонент CheckboxGroup может использовать одновременно useCheckboxGroupState и useCheckbox:

import { useCheckboxGroupState } from "@react-stately/checkbox";
import { useCheckbox } from "@react-aria/checkbox";

function CheckboxItem({ value, state }) {
  const { inputProps } = useCheckbox({ value }, state);

  return <input {...inputProps} />;
}

function CheckboxGroup({ options }) {
  const state = useCheckboxGroupState({ value: [], onChange: () => {} });

  return options.map(option => (
    <CheckboxItem key={option} value={option} state={state} />
  ));
}
  • Хук состояния useCheckboxGroupState отслеживает выбранные элементы.
  • Хук поведения useCheckbox использует это состояние для генерации корректных ARIA-атрибутов и обработчиков.

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


Взаимодействие через события и колбеки

React Aria активно использует колбеки и события для координации между хуками:

  • onChange, onFocus, onBlur передаются из поведения в состояние.
  • Поведение может инициировать вызовы методов состояния, например state.toggle() или state.setSelectedKeys().
  • Состояние может уведомлять внешние компоненты через колбеки, позволяя объединять внутреннее управление с внешним контролем.

Пример передачи событий между хуками:

function ComboBox({ items }) {
  const state = useComboBoxState({ items });
  const { inputProps, listBoxProps } = useComboBox({ inputRef: inputRef }, state);

  return (
    <>
      <input {...inputProps} ref={inputRef} />
      <ul {...listBoxProps}>
        {state.collection.map(item => (
          <li key={item.key}>{item.rendered}</li>
        ))}
      </ul>
    </>
  );
}

Здесь useComboBox использует состояние state для управления открытием списка, выделением элемента и обработкой клавиатуры.


Передача рефов между хуками

Многие хуки поведения требуют рефы на DOM-элементы. Правильное управление рефами критично для синхронизации:

  • useButton или useCheckbox принимает ref, который указывает на DOM-элемент.
  • Хуки состояния не знают о DOM, поэтому реф передается только в поведение.
  • Можно использовать forwardRef для проксирования рефов через несколько компонентов.

Пример:

const MyButton = React.forwardRef((props, ref) => {
  const { buttonProps } = useButton(props, ref);
  return <button {...buttonProps} ref={ref}>{props.children}</button>;
});

Синхронизация нескольких состояний

В больших приложениях может возникнуть необходимость синхронизации нескольких состояний между хуками. React Aria решает это через:

  1. Поднятие состояния (lifting state up) — передача состояния от дочернего к родительскому хуку.
  2. Использование контекста — хуки могут читать состояние из Context, что позволяет синхронизировать состояние группы компонентов.
  3. Колбеки onChange и эффекты — обновление внешнего состояния при изменении внутреннего, создавая реактивную цепочку между хуками.

Пример с контекстом:

const ListContext = React.createContext();

function ListProvider({ children }) {
  const state = useListState({ items: ["A", "B", "C"] });
  return <ListContext.Provider value={state}>{children}</ListContext.Provider>;
}

function ListItem({ key }) {
  const state = React.useContext(ListContext);
  return <div>{state.collection.getItem(key).rendered}</div>;
}

Важные принципы взаимодействия

  • Разделение логики и представления: хуки состояния не должны обращаться к DOM.
  • Поведение использует состояние как источник истины.
  • Рефы передаются только в хуки поведения.
  • Колбеки и события обеспечивают синхронизацию между хуками и компонентами.
  • Контекст упрощает объединение состояния между компонентами группы.

Эта архитектура делает компоненты React Aria модульными, предсказуемыми и доступными, позволяя создавать сложные интерфейсы без дублирования логики и с полной поддержкой ARIA.