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

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

  • Контейнерные компоненты (container components) — управляют состоянием и взаимодействиями, используют хуки React Aria для обработки событий, фокуса, клавиатурной навигации.
  • Презентационные компоненты (presentation components) — получают пропсы состояния и рендерят разметку, применяют стили и классы.

Пример: для создания доступного селекта можно использовать хук useSelect для управления открытием списка, выбранным элементом и фокусом, а визуальный компонент SelectList только рендерит список и обрабатывает визуальные состояния.

import { useSelect } from '@react-aria/sel ect';
import { useSelectState } fr om '@react-stately/sel ect';

function SelectContainer({ items }) {
  const state = useSelectState({ items });
  const ref = React.useRef();
  const { labelProps, triggerProps, valueProps, menuProps } = useSelect({ label: 'Выберите опцию' }, state, ref);

  return (
    <SelectList
      labelProps={labelProps}
      triggerProps={triggerProps}
      valueProps={valueProps}
      menuProps={menuProps}
      state={state}
      ref={ref}
    />
  );
}

Использование хуков для управления состоянием и доступностью

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

  1. Фокус и клавиатура

    • useFocusRing — определяет, находится ли элемент в фокусе, и автоматически применяет стили фокусировки.
    • useKeyboard — управляет клавиатурными событиями, обеспечивая стандартную навигацию (стрелки, Enter, Escape).
  2. Состояние выбора и управления данными

    • useSelect, useComboBox, useListBox — создают управляемое состояние для списков и выпадающих меню.
    • useTreeState, useTableState — управляют структурированными данными с поддержкой выделения и навигации.
  3. ARIA-атрибуты и роли

    • Хуки автоматически назначают правильные роли (role="listbox", role="option") и атрибуты (aria-selected, aria-expanded) для поддержания доступности.

Использование хуков повышает читаемость кода и снижает вероятность ошибок при ручном управлении ARIA-атрибутами.

Разделение состояния и DOM-структуры

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

function CustomSelect({ state, triggerProps, menuProps }) {
  return (
    <div>
      <button {...triggerProps}>{state.selectedItem?.name || 'Выберите'}</button>
      {state.isOpen && (
        <ul {...menuProps}>
          {state.collection.map(item => (
            <li key={item.key}>{item.rendered}</li>
          ))}
        </ul>
      )}
    </div>
  );
}

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

Интеграция с React Stately

React Aria часто используют вместе с библиотекой React Stately, которая предоставляет хуки управления состоянием. Принцип работы:

  • Stately хранит состояние (выбранные элементы, открытые меню, фокус, фильтры).
  • Aria предоставляет пропсы и функции для связывания состояния с DOM и ARIA-атрибутами.

Пример: управляемый чекбокс:

import { useCheckbox } fr om '@react-aria/checkbox';
import { useCheckboxState } from '@react-stately/checkbox';

function Checkbox({ label }) {
  const state = useCheckboxState({ defaultSelected: false });
  const ref = React.useRef();
  const { inputProps } = useCheckbox({ children: label }, state, ref);

  return (
    <label>
      <input {...inputProps} ref={ref} />
      {label}
    </label>
  );
}

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

Правильная организация кода в React Aria строится на следующих принципах:

  • Компоненты должны быть атомарными — один компонент отвечает за один визуальный или функциональный аспект.
  • Хуки состояния изолированы от UI — один и тот же хук состояния можно использовать с разными визуальными компонентами.
  • Передача пропсов через композицию — использование props и ref для связи состояния и разметки.
  • Согласованность с ARIA-спецификациями — все компоненты должны автоматически обеспечивать доступность.

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

Использование контекстов для глобального состояния

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

const FocusContext = React.createContext();

function FocusProvider({ children }) {
  const [currentFocus, setCurrentFocus] = React.useState(null);
  return (
    <FocusContext.Provider value={{ currentFocus, setCurrentFocus }}>
      {children}
    </FocusContext.Provider>
  );
}

function useFocusManager() {
  return React.useContext(FocusContext);
}

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

Структура файлов и организация проекта

Для крупных проектов рекомендуется следующая структура:

/components
  /Select
    SelectContainer.js   // логика состояния
    SelectList.js        // визуальное отображение
  /Checkbox
    CheckboxContainer.js
    CheckboxUI.js
/hooks
  useCustomFocus.js
  useFilteredList.js
/utils
  ariaUtils.js
  stateHelpers.js

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

Практика: композиция и переиспользование

Композиция компонентов позволяет создавать новые интерфейсы без дублирования логики:

function FilterableSelect({ items }) {
  const state = useSelectState({ items });
  const ref = React.useRef();
  const { triggerProps, menuProps } = useSelect({ label: 'Фильтруемый селект' }, state, ref);

  return (
    <div>
      <SearchInput state={state} />
      <SelectList state={state} triggerProps={triggerProps} menuProps={menuProps} ref={ref} />
    </div>
  );
}

Здесь SearchInput и SelectList могут быть использованы отдельно в других частях приложения, повторно используя один и тот же хук состояния.