Динамические коллекции

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

Использование useListData для управления коллекциями

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

Пример базовой структуры:

import {useListData} from '@react-stately/data';

function Example() {
  const list = useListData({
    initialItems: [
      {id: '1', name: 'Элемент 1'},
      {id: '2', name: 'Элемент 2'}
    ]
  });

  return (
    <div>
      {list.items.map(item => (
        <div key={item.id}>{item.name}</div>
      ))}
      <button onCl ick={() => list.append({id: '3', name: 'Элемент 3'})}>
        Добавить элемент
      </button>
    </div>
  );
}

Ключевые моменты работы с useListData:

  • append(item) — добавляет элемент в конец коллекции.
  • prepend(item) — добавляет элемент в начало.
  • upd ate(id, newItem) — обновляет элемент с указанным id.
  • remove(id) — удаляет элемент по идентификатору.
  • move(fromIndex, toIndex) — перемещает элемент внутри коллекции.

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

Интеграция с компонентами React Aria

Для корректной доступности динамических коллекций необходимо сочетать useListData с хуками React Aria, обеспечивающими ARIA-поддержку, например:

  • useListBox — для визуализации и управления списком.
  • useOption — для отдельного элемента списка.

Пример использования динамического списка с выделением:

import {useListBox, useOption} from '@react-aria/listbox';
import {useListData} from '@react-stately/data';
import {useSelectionState} from '@react-stately/selection';

function DynamicList() {
  const list = useListData({
    initialItems: [
      {id: '1', name: 'Элемент 1'},
      {id: '2', name: 'Элемент 2'}
    ]
  });

  const selection = useSelectionState({selectedKeys: new Se t()});

  const {listBoxProps} = useListBox({selection}, list);

  return (
    <ul {...listBoxProps}>
      {list.items.map(item => (
        <Option key={item.id} item={item} selection={selection} />
      ))}
    </ul>
  );
}

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

  return (
    <li {...optionProps} ref={ref} style={{background: isFocused ? 'lightblue' : 'transparent'}}>
      {item.name} {isSelected && '(выбрано)'}
    </li>
  );
}

Особенности:

  • useOption автоматически управляет состоянием фокуса и выделения для каждого элемента.
  • При динамическом добавлении или удалении элементов ARIA-атрибуты обновляются корректно, что обеспечивает совместимость с screen readers.
  • selectionState хранит текущее состояние выделения и синхронизируется с визуальными компонентами.

Обработка асинхронных коллекций

Динамические коллекции часто загружаются с сервера. В этом случае комбинация useListData с React state позволяет плавно интегрировать асинхронные данные.

import {useEffect} from 'react';
import {useListData} from '@react-stately/data';

function AsyncList() {
  const list = useListData({initialItems: []});

  useEffect(() => {
    fetch('/api/items')
      .then(res => res.json())
      .then(data => {
        data.forEach(item => list.append(item));
      });
  }, []);

  return (
    <ul>
      {list.items.map(item => (
        <li key={item.id}>{item.name}</li>
      ))}
    </ul>
  );
}

Важные моменты:

  • При обновлении списка после асинхронной загрузки необходимо использовать уникальные ключи (id), чтобы не ломалась фокусировка.
  • Можно комбинировать с useVirtualizer для оптимизации рендеринга длинных списков.

Поддержка сложных коллекций и группировка

React Aria позволяет организовывать коллекции с группами элементов. Для этого используются свойства children и type: 'section' в useListData.

const list = useListData({
  initialItems: [
    {id: 'fruits', name: 'Фрукты', type: 'section', children: [
      {id: 'apple', name: 'Яблоко'},
      {id: 'banana', name: 'Банан'}
    ]},
    {id: 'vegetables', name: 'Овощи', type: 'section', children: [
      {id: 'carrot', name: 'Морковь'}
    ]}
  ]
});

Особенности работы с группами:

  • ARIA-ролями автоматически назначаются group и listitem.
  • Выделение и фокусировка работают как на уровне группы, так и на уровне отдельных элементов.
  • Динамическое добавление элементов в группу не нарушает структуру и доступность.

Итоговые принципы работы с динамическими коллекциями

  1. Использовать useListData для централизованного управления состоянием коллекции.
  2. Присваивать уникальные ключи каждому элементу.
  3. Комбинировать с хуками React Aria (useListBox, useOption) для автоматической поддержки ARIA.
  4. Обрабатывать асинхронные данные через state или эффекты, корректно обновляя список.
  5. Использовать секции и группы, если коллекция имеет иерархическую структуру.
  6. Поддерживать выделение и фокусировку при изменении коллекции, используя useSelectionState и соответствующие props.

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