Сортировка столбцов

React Aria предоставляет мощный набор хуков и утилит для создания доступных интерфейсов, в том числе для работы с таблицами и сортируемыми столбцами. Основным инструментом для реализации сортировки является хук useTable совместно с useTableColumn и useSortBy, которые позволяют управлять состоянием сортировки, предоставлять корректные ARIA-атрибуты и интегрироваться с кастомной логикой рендеринга.


Хук useTable и структура данных

Для работы с таблицей необходимо определить столбцы и данные. Столбцы описываются объектами с ключами:

  • name – отображаемое название столбца
  • key – уникальный идентификатор столбца
  • sortable – флаг, определяющий, доступна ли сортировка

Пример структуры:

const columns = [
  { name: 'Имя', key: 'name', sortable: true },
  { name: 'Возраст', key: 'age', sortable: true },
  { name: 'Город', key: 'city', sortable: false }
];

const data = [
  { name: 'Алексей', age: 28, city: 'Москва' },
  { name: 'Мария', age: 34, city: 'Санкт-Петербург' },
  { name: 'Иван', age: 22, city: 'Казань' }
];

Хук useTable создаёт базовую структуру таблицы и возвращает методы для генерации ARIA-атрибутов и управления состоянием:

import { useTable } from '@react-aria/table';

const { tableProps, headerProps, rowProps } = useTable({ columns, data });

Сортировка через useSortBy

Для добавления сортировки используется хук useSortBy. Он принимает массив столбцов и текущее состояние сортировки, а возвращает обработчики для клика по заголовку и атрибуты для кнопок сортировки:

import { useSortBy } from '@react-aria/table';

const { sortDirection, sortButtonProps } = useSortBy({
  columns,
  sortState,
  onSortChange: setSortState
});

Где:

  • sortDirection – направление сортировки для конкретного столбца (ascending, descending или none)
  • sortButtonProps – ARIA-атрибуты для кнопки заголовка

Состояние сортировки может храниться локально через React useState:

const [sortState, setSortState] = useState({ key: null, direction: null });

Привязка к заголовкам столбцов

Заголовок столбца должен использовать свойства, возвращаемые useSortBy, чтобы обеспечить доступность:

<th {...headerProps}>
  <button {...sortButtonProps(column.key)}>
    {column.name}
    {sortDirection === 'ascending' ? ' ↑' : sortDirection === 'descending' ? ' ↓' : ''}
  </button>
</th>

Ключевые моменты:

  • Каждая кнопка заголовка получает правильные ARIA-атрибуты: aria-sort, aria-label
  • Состояние сортировки обновляется через onSortChange
  • Визуальная индикация ( / ) помогает пользователю понять текущее направление сортировки

Сортировка данных

После выбора столбца и направления необходимо отсортировать данные. Обычно используется Array.prototype.sort:

const sortedData = [...data].sort((a, b) => {
  const valueA = a[sortState.key];
  const valueB = b[sortState.key];

  if (valueA < valueB) return sortState.direction === 'ascending' ? -1 : 1;
  if (valueA > valueB) return sortState.direction === 'ascending' ? 1 : -1;
  return 0;
});

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

  • Сортировка должна быть стабильной, чтобы одинаковые значения не меняли порядок
  • Для строк рекомендуется использовать localeCompare для корректного сравнения символов
if (typeof valueA === 'string') {
  return sortState.direction === 'ascending'
    ? valueA.localeCompare(valueB)
    : valueB.localeCompare(valueA);
}

Поддержка нескольких уровней сортировки

React Aria позволяет расширять сортировку до нескольких столбцов. Для этого хранится массив объектов состояния сортировки:

const [sortStates, setSortStates] = useState([
  { key: 'age', direction: 'ascending' },
  { key: 'name', direction: 'descending' }
]);

Функция сортировки проходит по массиву sortStates и последовательно сравнивает значения:

sortedData.sort((a, b) => {
  for (let { key, direction } of sortStates) {
    if (a[key] < b[key]) return direction === 'ascending' ? -1 : 1;
    if (a[key] > b[key]) return direction === 'ascending' ? 1 : -1;
  }
  return 0;
});

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

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

function SortableHeader({ column }) {
  const { sortDirection, sortButtonProps } = useSortBy({ columns, sortState, onSortChange: setSortState });

  return (
    <div role="columnheader" {...sortButtonProps(column.key)}>
      {column.name}
      {sortDirection === 'ascending' ? ' ↑' : sortDirection === 'descending' ? ' ↓' : ''}
    </div>
  );
}

Преимущества такого подхода:

  • Лёгкая стилизация через CSS или styled-components
  • Сохранение ARIA-атрибутов и поведения клавиатуры
  • Возможность интеграции с виртуализированными таблицами

Поддержка клавиатурной навигации

Сортируемые столбцы в React Aria автоматически получают поддержку клавиатуры:

  • Tab – перемещение между заголовками
  • Enter / Space – смена направления сортировки
  • ARIA-атрибуты aria-sort сигнализируют о текущем состоянии

Для полного контроля можно комбинировать useTable и useFocusRing, чтобы визуально выделять активный элемент при навигации с клавиатуры.


Работа с динамическими данными

Если данные приходят асинхронно, состояние сортировки можно применять после загрузки:

useEffect(() => {
  if (data.length > 0 && sortState.key) {
    setSortedData(sortData(data, sortState));
  }
}, [data, sortState]);

Это позволяет поддерживать сортировку даже при изменении данных на сервере или при фильтрации.


Практические рекомендации

  • Всегда использовать key для столбцов, чтобы React корректно обновлял DOM
  • Для больших таблиц рекомендуется применять виртуализацию через react-virtual или react-window
  • Визуальная индикация сортировки должна быть понятной без reliance на цвет
  • При работе с датами или числами использовать соответствующие функции сравнения

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