Какие знаешь методы для отображения таблицы из 100000 полей?
Комментарии (2)
Ответ сгенерирован нейросетью и может содержать ошибки
Методы отображения больших таблиц (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 строк лучший подход:
- Виртуализация - основа (react-window)
- Pagination/Infinite scroll - загрузка порциями
- Поиск/фильтрация на бэкэнде - меньше данных отправлять
- Мемоизация - оптимизация рендера
- Server-Side Sorting - не сортировать 100000 элементов на клиенте
Никогда не пытайтесь отрендерить все 100000 строк одновременно. Это убьет браузер и пользовательский опыт.