Делегирование событий

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

Основная концепция

В стандартном React обработка событий осуществляется через props (onClick, onKeyDown, onFocus и т.д.) на конкретных компонентах. В React Aria подход делегирования событий расширяется, добавляя контекстный уровень управления событиями, что особенно важно для сложных интерактивных компонентов, таких как списки, таблицы или выпадающие меню.

Принцип работы:

  1. Один родительский элемент обрабатывает события дочерних компонентов.
  2. События фильтруются по типу и целевому элементу.
  3. Логика обработки централизована, что снижает дублирование кода и улучшает управляемость состояния.

Реализация делегирования в React Aria

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

  • useButton — для кнопок и интерактивных элементов, поддерживает клавиатурные и мышиные события.
  • useListBox и useOption — для списков и их элементов, делегируют события выбора.
  • useMenu и useMenuItem — для выпадающих меню с управлением фокусом и навигацией клавиатурой.
  • useFocusRing — для отслеживания фокуса и состояния взаимодействия с элементами.

Пример делегирования события клика на список элементов:

import { useListState } from '@react-stately/list';
import { useListBox, useOption } from '@react-aria/listbox';

function ListBox({ items }) {
  const state = useListState({ items });
  const ref = React.useRef();
  const { listBoxProps } = useListBox({ autoFocus: true }, state, ref);

  return (
    <ul {...listBoxProps} ref={ref}>
      {state.collection.map(item => (
        <Option key={item.key} item={item} state={state} />
      ))}
    </ul>
  );
}

function Option({ item, state }) {
  const ref = React.useRef();
  const { optionProps, isSelected, isFocused } = useOption({ key: item.key }, state, ref);

  return (
    <li {...optionProps} ref={ref} style={{
      background: isFocused ? 'lightgray' : 'white',
      fontWeight: isSelected ? 'bold' : 'normal'
    }}>
      {item.rendered}
    </li>
  );
}

В этом примере:

  • useListBox делегирует управление событиями на весь список.
  • useOption обеспечивает корректное взаимодействие каждой опции с контекстом списка.
  • Не нужно назначать onClick или onKeyDown на каждый <li> вручную — обработка событий централизована.

Преимущества делегирования

  • Оптимизация производительности: меньше обработчиков на DOM-элементах, снижается нагрузка на виртуальный DOM.
  • Универсальность управления: клавиатурная и мышиная навигация управляется через единый контекст.
  • Доступность: автоматическая поддержка ARIA-атрибутов и фокусировки, что облегчает создание компонентов для пользователей с особыми потребностями.
  • Поддержка сложных структур: деревья, таблицы и списки с большим количеством элементов управляются через один обработчик.

Работа с клавиатурой и фокусом

В React Aria делегирование событий тесно связано с управлением фокусом. Например, при навигации по списку стрелками клавиатуры, события onKeyDown обрабатываются на уровне useListBox, а каждый useOption реагирует только на изменение состояния (выбор, фокус).

function KeyboardNavigableList({ items }) {
  const state = useListState({ items });
  const ref = React.useRef();
  const { listBoxProps } = useListBox({}, state, ref);

  return (
    <ul {...listBoxProps} ref={ref}>
      {state.collection.map(item => (
        <Option key={item.key} item={item} state={state} />
      ))}
    </ul>
  );
}

Здесь делегирование событий клавиатуры позволяет перемещать фокус между элементами без необходимости назначать обработчики на каждый <li>.

Обработка пользовательских событий

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

Пример: делегирование кастомного события onActivate для кнопок:

function CustomButton(props) {
  const ref = React.useRef();
  const { buttonProps } = useButton({ ...props, onPress: props.onActivate }, ref);

  return <button {...buttonProps} ref={ref}>{props.children}</button>;
}
  • onPress в useButton делегируется на родительский компонент или на контекст, если кнопка находится внутри группы.
  • Можно легко интегрировать дополнительные состояния: isPressed, isDisabled или isHovered.

Рекомендации по применению

  • Использовать делегирование для списков, таблиц, меню и групп интерактивных элементов.
  • Включать управление фокусом и клавиатурной навигацией через соответствующие хуки React Aria.
  • Не создавать отдельный обработчик на каждый дочерний элемент, если это не критично для индивидуального поведения.
  • Следить за ARIA-атрибутами, которые автоматически управляются хуками, чтобы сохранялась доступность.

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