Реализация интерактивных таблиц (DataTables/TanStack Table) на сайте

Наша компания занимается разработкой, поддержкой и обслуживанием сайтов любой сложности. От простых одностраничных сайтов до масштабных кластерных систем построенных на микро сервисах. Опыт разработчиков подтвержден сертификатами от вендоров.

Разработка и обслуживание любых видов сайтов:

Информационные сайты или веб-приложения
Сайты визитки, landing page, корпоративные сайты, онлайн каталоги, квиз, промо-сайты, блоги, новостные ресурсы, информационные порталы, форумы, агрегаторы
Сайты или веб-приложения электронной коммерции
Интернет-магазины, B2B-порталы, маркетплейсы, онлайн-обменники, кэшбэк-сайты, биржи, дропшиппинг-платформы, парсеры товаров
Веб-приложения для управления бизнес-процессами
CRM-системы, ERP-системы, корпоративные порталы, системы управления производством, парсеры информации
Сайты или веб-приложения электронных услуг
Доски объявлений, онлайн-школы, онлайн-кинотеатры, конструкторы сайтов, порталы предоставления электронных услуг, видеохостинги, тематические порталы

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

Предлагаемые услуги
Показано 1 из 1 услугВсе 2065 услуг
Реализация интерактивных таблиц (DataTables/TanStack Table) на сайте
Средняя
~3-5 рабочих дней
Часто задаваемые вопросы

Наши компетенции:

Этапы разработки

Последние работы

  • image_website-b2b-advance_0.png
    Разработка сайта компании B2B ADVANCE
    1262
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1171
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    874
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1094
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    831
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Разработка веб-сайта для компании ФИКСПЕР
    851

Реализация интерактивных таблиц (DataTables/TanStack Table) на сайте

Таблица с сортировкой и пагинацией — одна из самых частых задач во фронтенде, и одна из самых часто реализуемых плохо. jQuery DataTables всё ещё встречается в легаси-проектах, но в современных React-приложениях стандартом де-факто стал TanStack Table (ранее react-table) — headless-библиотека без встроенных стилей, которая даёт полный контроль над разметкой.

Выбор библиотеки

TanStack Table v8 — для React/Vue/Solid/Svelte проектов. Headless: никаких стилей в комплекте, только логика. Размер ~14 КБ gzipped.

jQuery DataTables — оправдан только если сайт уже использует jQuery и нет смысла добавлять React ради одной таблицы. В остальных случаях — не стоит.

AG Grid Community — когда нужна виртуализация на 100 000+ строк, Excel-экспорт и редактирование ячеек. Тяжелее, но мощнее.

TanStack Table: базовая реализация

npm install @tanstack/react-table

Определяем колонки и подключаем хук:

import {
  createColumnHelper,
  flexRender,
  getCoreRowModel,
  getSortedRowModel,
  getPaginationRowModel,
  getFilteredRowModel,
  useReactTable,
  SortingState,
} from '@tanstack/react-table'

type Order = {
  id: string
  customer: string
  amount: number
  status: 'pending' | 'paid' | 'cancelled'
  createdAt: string
}

const columnHelper = createColumnHelper<Order>()

const columns = [
  columnHelper.accessor('id', {
    header: '№ заказа',
    cell: (info) => <span className="font-mono text-sm">{info.getValue()}</span>,
  }),
  columnHelper.accessor('customer', {
    header: 'Клиент',
    enableSorting: true,
  }),
  columnHelper.accessor('amount', {
    header: 'Сумма',
    cell: (info) => `${info.getValue().toLocaleString('ru-RU')} ₽`,
    sortingFn: 'basic',
  }),
  columnHelper.accessor('status', {
    header: 'Статус',
    cell: (info) => <StatusBadge status={info.getValue()} />,
    enableSorting: false,
  }),
  columnHelper.accessor('createdAt', {
    header: 'Дата',
    sortingFn: 'datetime',
  }),
]
function OrdersTable({ data }: { data: Order[] }) {
  const [sorting, setSorting] = useState<SortingState>([])
  const [globalFilter, setGlobalFilter] = useState('')

  const table = useReactTable({
    data,
    columns,
    state: { sorting, globalFilter },
    onSortingChange: setSorting,
    onGlobalFilterChange: setGlobalFilter,
    getCoreRowModel: getCoreRowModel(),
    getSortedRowModel: getSortedRowModel(),
    getPaginationRowModel: getPaginationRowModel(),
    getFilteredRowModel: getFilteredRowModel(),
    initialState: { pagination: { pageSize: 25 } },
  })

  return (
    <div>
      <input
        value={globalFilter}
        onChange={(e) => setGlobalFilter(e.target.value)}
        placeholder="Поиск по всем полям..."
        className="mb-4 w-64 border rounded px-3 py-2"
      />
      <table className="w-full border-collapse">
        <thead>
          {table.getHeaderGroups().map((headerGroup) => (
            <tr key={headerGroup.id}>
              {headerGroup.headers.map((header) => (
                <th
                  key={header.id}
                  onClick={header.column.getToggleSortingHandler()}
                  className={header.column.getCanSort() ? 'cursor-pointer select-none' : ''}
                >
                  {flexRender(header.column.columnDef.header, header.getContext())}
                  {{ asc: ' ↑', desc: ' ↓' }[header.column.getIsSorted() as string] ?? ''}
                </th>
              ))}
            </tr>
          ))}
        </thead>
        <tbody>
          {table.getRowModel().rows.map((row) => (
            <tr key={row.id} className="hover:bg-gray-50">
              {row.getVisibleCells().map((cell) => (
                <td key={cell.id}>
                  {flexRender(cell.column.columnDef.cell, cell.getContext())}
                </td>
              ))}
            </tr>
          ))}
        </tbody>
      </table>
      <div className="flex items-center gap-2 mt-4">
        <button onClick={() => table.previousPage()} disabled={!table.getCanPreviousPage()}>
          ←
        </button>
        <span>
          Страница {table.getState().pagination.pageIndex + 1} из {table.getPageCount()}
        </span>
        <button onClick={() => table.nextPage()} disabled={!table.getCanNextPage()}>
          →
        </button>
        <select
          value={table.getState().pagination.pageSize}
          onChange={(e) => table.setPageSize(Number(e.target.value))}
        >
          {[10, 25, 50, 100].map((size) => (
            <option key={size} value={size}>по {size}</option>
          ))}
        </select>
      </div>
    </div>
  )
}

Серверная пагинация

Для больших датасетов (10 000+ строк) пагинация на клиенте не работает — всё должно идти через API:

const [{ pageIndex, pageSize }, setPagination] = useState({
  pageIndex: 0,
  pageSize: 25,
})

// React Query для загрузки данных
const { data, isFetching } = useQuery({
  queryKey: ['orders', pageIndex, pageSize, sorting, globalFilter],
  queryFn: () =>
    fetchOrders({
      page: pageIndex + 1,
      limit: pageSize,
      sortBy: sorting[0]?.id,
      sortDir: sorting[0]?.desc ? 'desc' : 'asc',
      search: globalFilter,
    }),
  keepPreviousData: true, // не мигает при переходе между страницами
})

const table = useReactTable({
  data: data?.rows ?? [],
  columns,
  pageCount: data?.pageCount ?? -1,
  state: { sorting, pagination: { pageIndex, pageSize }, globalFilter },
  manualPagination: true,  // ключевой флаг
  manualSorting: true,
  manualFiltering: true,
  onPaginationChange: setPagination,
  // ...
})

Экспорт в CSV

function exportToCSV(table: Table<Order>) {
  const headers = table.getAllColumns()
    .filter((col) => col.getIsVisible())
    .map((col) => col.columnDef.header as string)

  const rows = table.getFilteredRowModel().rows.map((row) =>
    row.getVisibleCells().map((cell) => {
      const value = cell.getValue()
      return typeof value === 'string' && value.includes(',') ? `"${value}"` : value
    })
  )

  const csv = [headers, ...rows].map((r) => r.join(',')).join('\n')
  const blob = new Blob(['\uFEFF' + csv], { type: 'text/csv;charset=utf-8' })
  const url = URL.createObjectURL(blob)
  const a = document.createElement('a')
  a.href = url
  a.download = `orders-${Date.now()}.csv`
  a.click()
  URL.revokeObjectURL(url)
}

\uFEFF — BOM для корректного отображения кириллицы в Excel.

Виртуализация строк

Для таблиц с тысячами строк на клиенте — TanStack Virtual:

npm install @tanstack/react-virtual
import { useVirtualizer } from '@tanstack/react-virtual'

const tableContainerRef = useRef<HTMLDivElement>(null)
const { rows } = table.getRowModel()

const rowVirtualizer = useVirtualizer({
  count: rows.length,
  getScrollElement: () => tableContainerRef.current,
  estimateSize: () => 48, // высота строки в px
  overscan: 10,
})

// В JSX:
<div ref={tableContainerRef} style={{ height: '600px', overflow: 'auto' }}>
  <table>
    <tbody style={{ height: `${rowVirtualizer.getTotalSize()}px`, position: 'relative' }}>
      {rowVirtualizer.getVirtualItems().map((virtualRow) => {
        const row = rows[virtualRow.index]
        return (
          <tr
            key={row.id}
            style={{
              position: 'absolute',
              top: 0,
              transform: `translateY(${virtualRow.start}px)`,
              height: `${virtualRow.size}px`,
            }}
          >
            {row.getVisibleCells().map((cell) => (
              <td key={cell.id}>
                {flexRender(cell.column.columnDef.cell, cell.getContext())}
              </td>
            ))}
          </tr>
        )
      })}
    </tbody>
  </table>
</div>

10 000 строк рендерятся за ~20 мс — в DOM присутствует только то, что видно в viewport.

Что делаем

Анализируем структуру данных и требования: объём, частота обновлений, нужен ли inline-edit, экспорт, фиксированные колонки. Под задачу выбираем клиентскую или серверную модель пагинации, настраиваем колонки, сортировку, фильтрацию. Стилизуем под дизайн-систему проекта — таблица выглядит как часть интерфейса, а не вставка из другого приложения.

Срок: базовая таблица с сортировкой и пагинацией — 1 день. С серверной пагинацией, фильтрами и экспортом — 2–3 дня.