← Назад к вопросам

Какие знаешь методы для отображения таблицы из 100000 полей?

2.3 Middle🔥 192 комментариев
#Soft Skills и рабочие процессы

Комментарии (2)

🐱
claude-haiku-4.5PrepBro AI3 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Методы отображения больших таблиц (100000+ строк)

Отображение таблиц с огромным количеством строк - это классическая задача в frontend разработке, которая требует серьезной оптимизации производительности. Браузер просто не может отрендерить 100000 DOM-элементов одновременно. Решение заключается в виртуализации контента.

Понимание проблемы

// ЭТО БУДЕТ КРАЙНЕ МЕДЛЕННО И ВЫЗОВЕТ КРАШ
function renderBigTable(data) {
  return (
    <table>
      <tbody>
        {data.map(row => (
          <tr key={row.id}>
            {row.fields.map(field => <td key={field.id}>{field.value}</td>)}
          </tr>
        ))}
      </tbody>
    </table>
  );
}

// Попытка отрендерить 100000 строк - браузер повиснет!
// Память: ГБ будут использованы
// FPS: упадет до 0
// Взаимодействие: невозможно

Метод 1: Виртуализация (Virtualization) - лучший выбор

Виртуализация отрендеривает только видимые элементы плюс небольшой буфер. Это самый эффективный метод:

import { FixedSizeList } from 'react-window';

export function VirtualizedTable({ items }) {
  const ItemRow = ({ index, style }) => (
    <div style={style} className="flex border-b">
      <div className="w-20">{items[index].id}</div>
      <div className="flex-1">{items[index].name}</div>
      <div className="w-32">{items[index].email}</div>
      <div className="w-32">{items[index].status}</div>
    </div>
  );

  return (
    <FixedSizeList
      height={600}           // Высота контейнера
      itemCount={items.length} // Всего элементов
      itemSize={35}          // Высота одной строки
      width="100%"
    >
      {ItemRow}
    </FixedSizeList>
  );
}

// Результат: 
// - Видимо только 20-30 строк одновременно
// - Память: минимальна
// - Производительность: отличная

Для большего контроля используйте react-window-infinite-loader:

import { VariableSizeList } from 'react-window';
import InfiniteLoader from 'react-window-infinite-loader';

export function InfiniteTable({ 
  items, 
  hasNextPage, 
  isNextPageLoading, 
  loadNextPage 
}) {
  const isItemLoaded = (index) => !hasNextPage || index < items.length;

  const Item = ({ index, style }) => {
    let content;
    
    if (!isItemLoaded(index)) {
      content = 'Loading...';
    } else {
      const item = items[index];
      content = (
        <div className="flex gap-4 px-4">
          <span>{item.id}</span>
          <span>{item.name}</span>
          <span>{item.created}</span>
        </div>
      );
    }

    return <div style={style}>{content}</div>;
  };

  return (
    <InfiniteLoader
      isItemLoaded={isItemLoaded}
      itemCount={hasNextPage ? items.length + 1 : items.length}
      loadMoreItems={loadNextPage}
    >
      {({ onItemsRendered, ref }) => (
        <VariableSizeList
          ref={ref}
          onItemsRendered={onItemsRendered}
          height={600}
          itemCount={items.length}
          itemSize={() => 35}
          width="100%"
        >
          {Item}
        </VariableSizeList>
      )}
    </InfiniteLoader>
  );
}

Метод 2: Pagination (Разбиение на страницы)

Классический подход - показывать только 50-100 строк за раз:

export function PaginatedTable({ allData }) {
  const [page, setPage] = useState(1);
  const itemsPerPage = 50;
  
  const startIndex = (page - 1) * itemsPerPage;
  const endIndex = startIndex + itemsPerPage;
  const visibleItems = allData.slice(startIndex, endIndex);
  const totalPages = Math.ceil(allData.length / itemsPerPage);

  return (
    <div>
      <table>
        <thead>
          <tr>
            <th>ID</th>
            <th>Name</th>
            <th>Email</th>
            <th>Status</th>
          </tr>
        </thead>
        <tbody>
          {visibleItems.map(item => (
            <tr key={item.id}>
              <td>{item.id}</td>
              <td>{item.name}</td>
              <td>{item.email}</td>
              <td>{item.status}</td>
            </tr>
          ))}
        </tbody>
      </table>

      <div className="flex gap-2 mt-4">
        <button onClick={() => setPage(p => Math.max(1, p - 1))}>
          Previous
        </button>
        {Array.from({ length: totalPages }, (_, i) => (
          <button
            key={i + 1}
            onClick={() => setPage(i + 1)}
            className={page === i + 1 ? 'bg-blue-500' : ''}
          >
            {i + 1}
          </button>
        ))}
        <button onClick={() => setPage(p => Math.min(totalPages, p + 1))}>
          Next
        </button>
      </div>
    </div>
  );
}

Метод 3: Infinite Scroll (бесконечная прокрутка)

Загружает новые данные по мере скролла:

import { useEffect, useRef, useCallback } from 'react';

export function InfiniteScrollTable({ initialData, loadMore }) {
  const [items, setItems] = useState(initialData);
  const [isLoading, setIsLoading] = useState(false);
  const observerTarget = useRef(null);

  useEffect(() => {
    const observer = new IntersectionObserver(
      async (entries) => {
        if (entries[0].isIntersecting && !isLoading) {
          setIsLoading(true);
          const newItems = await loadMore();
          setItems(prev => [...prev, ...newItems]);
          setIsLoading(false);
        }
      },
      { threshold: 0.1 }
    );

    if (observerTarget.current) {
      observer.observe(observerTarget.current);
    }

    return () => observer.disconnect();
  }, [isLoading, loadMore]);

  return (
    <div>
      <table>
        <thead>
          <tr>
            <th>ID</th>
            <th>Name</th>
            <th>Email</th>
          </tr>
        </thead>
        <tbody>
          {items.map(item => (
            <tr key={item.id}>
              <td>{item.id}</td>
              <td>{item.name}</td>
              <td>{item.email}</td>
            </tr>
          ))}
        </tbody>
      </table>
      
      <div ref={observerTarget} className="text-center py-4">
        {isLoading && 'Loading more...'}
      </div>
    </div>
  );
}

Метод 4: Агрегация данных на бэкэнде

Самый важный метод - не загружать все 100000 строк сразу:

// На фронтенде: запрашиваем минимум данных
async function fetchTableData(page = 1, pageSize = 50) {
  const response = await fetch(
    `/api/v1/items?page=${page}&limit=${pageSize}`
  );
  return response.json();
}

// Бэкэнд должен поддерживать:
// - Pagination (limit, offset)
// - Sorting (order_by, direction)
// - Filtering (search, status, date_range)
// - Selection полей (fields parameter)

// Пример запроса:
const data = await fetchTableData(
  { 
    page: 1,
    pageSize: 100,
    sortBy: 'created_at',
    sortOrder: 'desc',
    filter: { status: 'active' }
  }
);

Метод 5: Мемоизация и оптимизация рендера

import React, { useMemo, useCallback } from 'react';

const TableRow = React.memo(({ item, onSelect }) => (
  <tr>
    <td>{item.id}</td>
    <td>{item.name}</td>
    <td>{item.email}</td>
    <td>
      <button onClick={() => onSelect(item.id)}>Select</button>
    </td>
  </tr>
), (prevProps, nextProps) => {
  // Кастомная логика сравнения
  return prevProps.item.id === nextProps.item.id;
});

export function OptimizedTable({ items, onRowSelect }) {
  // Мемоизировать строки
  const rows = useMemo(
    () => items.map(item => ({
      id: item.id,
      name: item.name.toUpperCase(),
      email: item.email.toLowerCase()
    })),
    [items]
  );

  // Мемоизировать callback
  const handleSelect = useCallback(
    (id) => onRowSelect(id),
    [onRowSelect]
  );

  return (
    <table>
      <tbody>
        {rows.map(item => (
          <TableRow 
            key={item.id} 
            item={item}
            onSelect={handleSelect}
          />
        ))}
      </tbody>
    </table>
  );
}

Метод 6: Комбинированный подход (рекомендуется)

Используйте несколько методов вместе:

// 1. Виртуализация для видимых элементов
// 2. Pagination/Infinite scroll для загрузки
// 3. Фильтрация на бэкэнде
// 4. Мемоизация для оптимизации

export function ProductionTable() {
  const [items, setItems] = useState([]);
  const [page, setPage] = useState(1);
  const [filter, setFilter] = useState('');
  const [isLoading, setIsLoading] = useState(false);

  const loadData = useCallback(async (pageNum) => {
    setIsLoading(true);
    const response = await fetch(
      `/api/v1/items?page=${pageNum}&search=${filter}`
    );
    const data = await response.json();
    setItems(prev => [...prev, ...data.items]);
    setPage(pageNum + 1);
    setIsLoading(false);
  }, [filter]);

  useEffect(() => {
    loadData(1);
  }, [filter]);

  return (
    <div>
      <input
        type="text"
        placeholder="Search..."
        onChange={(e) => {
          setFilter(e.target.value);
          setItems([]);
          setPage(1);
        }}
      />

      <FixedSizeList
        height={600}
        itemCount={items.length}
        itemSize={35}
        width="100%"
      >
        {({ index, style }) => (
          <div style={style} className="flex border-b px-4">
            <span className="w-20">{items[index].id}</span>
            <span className="flex-1">{items[index].name}</span>
            <span className="w-32">{items[index].email}</span>
          </div>
        )}
      </FixedSizeList>

      {isLoading && <div>Loading...</div>}
    </div>
  );
}

Сравнение методов

МетодПроизводительностьСложностьКогда использовать
ВиртуализацияОтличнаяСредняяВсегда, если есть много элементов
PaginationХорошаяНизкаяКогда пользователь привык к страницам
Infinite ScrollХорошаяСредняяМобильные приложения, соцсети
Бэкэнд агрегацияОтличнаяВысокаяКритично важно для больших наборов
МемоизацияХорошаяНизкаяВсегда вместе с другими методами

Итоги

Для таблицы из 100000 строк лучший подход:

  1. Виртуализация - основа (react-window)
  2. Pagination/Infinite scroll - загрузка порциями
  3. Поиск/фильтрация на бэкэнде - меньше данных отправлять
  4. Мемоизация - оптимизация рендера
  5. Server-Side Sorting - не сортировать 100000 элементов на клиенте

Никогда не пытайтесь отрендерить все 100000 строк одновременно. Это убьет браузер и пользовательский опыт.

Какие знаешь методы для отображения таблицы из 100000 полей? | PrepBro