Расчёт видимых элементов

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


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

useOverlay и useModal

Хук useOverlay отвечает за управление появлением и исчезновением элементов интерфейса, таких как всплывающие панели или меню. Основные параметры:

import { useOverlay } from "@react-aria/overlays";

function MyOverlay({ isOpen, onClose }) {
  let ref = useRef();
  let { overlayProps } = useOverlay({ isOpen, onClose, isDismissable: true }, ref);

  return (
    isOpen && (
      <div {...overlayProps} ref={ref}>
        Содержимое оверлея
      </div>
    )
  );
}
  • isOpen — состояние видимости элемента.
  • onClose — функция, вызываемая при закрытии.
  • isDismissable — позволяет закрывать оверлей кликом вне его области.

useFocusRing для визуализации состояния

useFocusRing помогает выделять элементы, которые находятся в фокусе. Это важно для доступности при динамическом отображении элементов:

import { useFocusRing } from "@react-aria/focus";

function FocusableButton() {
  let { isFocusVisible, focusProps } = useFocusRing();
  return (
    <button {...focusProps} style={{ outline: isFocusVisible ? '2px solid blue' : 'none' }}>
      Кнопка
    </button>
  );
}

Динамическое отображение списков

Для динамических списков (например, фильтруемых таблиц или меню) React Aria предоставляет хуки:

  • useListState — управляет состоянием элементов списка.
  • useListBox — добавляет доступные атрибуты для списков.

Пример:

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

function MyListBox({ items }) {
  let state = useListState({ items });
  let ref = useRef();
  let { listBoxProps } = useListBox({ ref });

  return (
    <ul {...listBoxProps} ref={ref}>
      {[...state.collection].map(item => {
        let { optionProps, isSelected } = useOption({ key: item.key }, state);
        return (
          <li {...optionProps} style={{ background: isSelected ? 'lightgray' : 'white' }}>
            {item.rendered}
          </li>
        );
      })}
    </ul>
  );
}
  • state.collection — коллекция всех элементов.
  • isSelected — управляет визуальным состоянием выделения.
  • optionProps — автоматически добавляет aria-selected и обработчики событий.

Контроль видимости с useHover и usePress

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

import { useHover, usePress } from "@react-aria/interactions";

function InteractiveBox() {
  let ref = useRef();
  let { hoverProps, isHovered } = useHover({ ref });
  let { pressProps, isPressed } = usePress({ ref });

  return (
    <div
      {...hoverProps}
      {...pressProps}
      ref={ref}
      style={{
        background: isHovered ? 'lightblue' : 'white',
        transform: isPressed ? 'scale(0.95)' : 'scale(1)',
      }}
    >
      Интерактивный блок
    </div>
  );
}
  • isHovered и isPressed — используются для динамической визуальной обратной связи.
  • Эти состояния позволяют корректно управлять динамически видимыми элементами, особенно в меню и всплывающих подсказках.

Оптимизация рендеринга

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

  • Использование хуков useVirtualizer или библиотек типа react-virtual позволяет рассчитывать только видимые элементы, снижая нагрузку на DOM.
  • Комбинация с useListState или useTableState делает виртуализацию доступной и совместимой с ARIA.
import { useVirtualizer } from '@tanstack/react-virtual';

function VirtualList({ items }) {
  const parentRef = useRef();
  const rowVirtualizer = useVirtualizer({
    count: items.length,
    getScrollElement: () => parentRef.current,
    estimateSize: () => 50,
  });

  return (
    <div ref={parentRef} style={{ height: '400px', overflow: 'auto' }}>
      <div style={{ height: `${rowVirtualizer.getTotalSize()}px`, position: 'relative' }}>
        {rowVirtualizer.getVirtualItems().map(virtualRow => (
          <div
            key={virtualRow.index}
            style={{
              position: 'absolute',
              top: 0,
              left: 0,
              width: '100%',
              transform: `translateY(${virtualRow.start}px)`,
            }}
          >
            {items[virtualRow.index]}
          </div>
        ))}
      </div>
    </div>
  );
}
  • Рендерятся только видимые строки, что позволяет эффективно обрабатывать тысячи элементов.
  • Совместимость с ARIA достигается путем добавления нужных role и aria-* атрибутов через useListBox или useTable.

Контроль доступности видимых элементов

Ключевой момент — все динамически отображаемые элементы должны корректно оповещать вспомогательные технологии:

  • Использование aria-hidden для скрытых элементов, чтобы они не мешали навигации.
  • Применение role="dialog" или role="menu" для контейнеров с оверлеями.
  • Автоматическое управление фокусом через focusScope из @react-aria/focus:
import { FocusScope } from "@react-aria/focus";

<FocusScope contain restoreFocus autoFocus>
  <div role="dialog">
    Контент модального окна
  </div>
</FocusScope>
  • contain ограничивает фокус внутри оверлея.
  • restoreFocus возвращает фокус к предыдущему элементу после закрытия.
  • autoFocus автоматически устанавливает фокус на первый доступный элемент.

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