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

Будет ли зависать Frontend при ста тысячах строк?

2.0 Middle🔥 81 комментариев
#Архитектура и паттерны

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

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

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

Будет ли зависать Frontend при ста тысячах строк?

Этот вопрос касается производительности браузера при отображении большого количества элементов. Короткий ответ: Да, зависнет. Давайте разберёмся, почему и как это решать.

Почему 100,000 строк — это проблема?

1. DOM слишком большой

// ❌ Наивный подход — рендерим все элементы в DOM
const items = Array(100000).fill(null).map((_, i) => `Item ${i}`);
const html = items.map(item => `<div>${item}</div>`).join('');
document.body.innerHTML = html;
// Результат: браузер зависает на 5-30 секунд

2. Paint и Reflow

  • Браузер должен "нарисовать" каждый элемент
  • При изменении любого элемента пересчитывает макет для всех
  • 100k элементов = 100k отрисовок

3. Память

  • Каждый DOM элемент занимает ~200+ байт памяти
  • 100k элементов = 20+ мегабайт только на DOM
  • На мобильных это критично

4. JavaScript выполнение

  • Парсинг и создание 100k объектов займёт время
  • Event listeners на каждом элементе = ещё больше памяти

Решение 1: Виртуализация (Virtual Scrolling)

Идея: рендерим только видимые элементы!

// ✅ Virtual Scrolling
class VirtualScroller {
    constructor(container, items, itemHeight) {
        this.container = container;
        this.items = items;
        this.itemHeight = itemHeight;
        this.visibleCount = Math.ceil(container.clientHeight / itemHeight);
        this.scrollTop = 0;
        
        this.container.addEventListener('scroll', () => this.onScroll());
        this.render();
    }
    
    onScroll() {
        this.scrollTop = this.container.scrollTop;
        this.render();
    }
    
    render() {
        // Вычисляем какие элементы видны
        const startIndex = Math.floor(this.scrollTop / this.itemHeight);
        const endIndex = startIndex + this.visibleCount + 1;
        
        // Рендерим только видимые элементы
        const html = this.items
            .slice(startIndex, endIndex)
            .map((item, i) => `
                <div style="position: absolute; top: ${(startIndex + i) * this.itemHeight}px;">
                    ${item}
                </div>
            `)
            .join('');
        
        this.container.innerHTML = html;
    }
}

// Использование
const items = Array(100000).fill(null).map((_, i) => `Item ${i}`);
const scroller = new VirtualScroller(
    document.getElementById('list'),
    items,
    50  // высота каждого элемента
);
// Результат: мгновенная загрузка, плавный скролл!

Производительность:

  • Время загрузки: < 100ms (вместо 30 секунд)
  • RAM использование: ~5MB (вместо 20MB+)
  • Плавный скролл на 60 FPS

React: примеры с react-window

import { FixedSizeList as List } from 'react-window';

// ❌ Без виртуализации (зависает)
function BadList({ items }) {
    return (
        <div>
            {items.map(item => (
                <div key={item.id}>{item.name}</div>
            ))}
        </div>
    );
}

// ✅ С виртуализацией (быстро)
function GoodList({ items }) {
    const Row = ({ index, style }) => (
        <div style={style}>
            {items[index].name}
        </div>
    );
    
    return (
        <List
            height={600}
            itemCount={items.length}
            itemSize={35}
            width="100%"
        >
            {Row}
        </List>
    );
}

// Использование
const items = Array(100000).fill(null).map((_, i) => ({
    id: i,
    name: `Item ${i}`
}));

ReactDOM.render(
    <GoodList items={items} />,
    document.getElementById('root')
);

Решение 2: Пагинация

// ✅ Простая пагинация
class PaginatedList {
    constructor(allItems, pageSize = 50) {
        this.allItems = allItems;
        this.pageSize = pageSize;
        this.currentPage = 0;
    }
    
    getPage(pageNumber = this.currentPage) {
        const start = pageNumber * this.pageSize;
        const end = start + this.pageSize;
        return this.allItems.slice(start, end);
    }
    
    nextPage() {
        this.currentPage++;
        return this.getPage();
    }
    
    get totalPages() {
        return Math.ceil(this.allItems.length / this.pageSize);
    }
}

// Использование
const list = new PaginatedList(Array(100000).fill(null), 50);
console.log(list.getPage()); // Первые 50 элементов
console.log(list.getPage(1)); // Элементы 50-100

Решение 3: Infinite Scroll (ленивая загрузка)

// ✅ Infinite scroll
class InfiniteScroll {
    constructor(container, fetchFn) {
        this.container = container;
        this.fetchFn = fetchFn;
        this.page = 0;
        this.isLoading = false;
        
        this.container.addEventListener('scroll', () => this.checkScroll());
        this.load();
    }
    
    async load() {
        if (this.isLoading) return;
        this.isLoading = true;
        
        const items = await this.fetchFn(this.page);
        items.forEach(item => {
            const div = document.createElement('div');
            div.textContent = item;
            this.container.appendChild(div);
        });
        
        this.page++;
        this.isLoading = false;
    }
    
    checkScroll() {
        const { scrollTop, clientHeight, scrollHeight } = this.container;
        
        // Если пользователь прокручивает к концу
        if (scrollTop + clientHeight > scrollHeight - 200) {
            this.load();
        }
    }
}

// Использование
const scroller = new InfiniteScroll(
    document.getElementById('list'),
    async (page) => {
        // API возвращает 50 элементов
        const response = await fetch(`/api/items?page=${page}&limit=50`);
        return response.json();
    }
);

Решение 4: Server-Side Фильтрация и Поиск

// ✅ Вместо загрузки всех 100k — фильтруем на сервере
class FilteredList {
    constructor(searchInput, container) {
        this.searchInput = searchInput;
        this.container = container;
        
        this.searchInput.addEventListener('input', (e) => this.search(e.target.value));
    }
    
    async search(query) {
        const response = await fetch(`/api/search?q=${query}`);
        const results = await response.json();
        
        this.container.innerHTML = results
            .map(item => `<div>${item}</div>`)
            .join('');
    }
}

// Использование
const list = new FilteredList(
    document.getElementById('searchInput'),
    document.getElementById('results')
);

Бенчмарк: разные подходы

ПодходПамятьЗагрузкаСкроллПоиск
Все строки20MB+30s❌ ЗависаетНевозможен
Виртуализация2MB100ms✅ 60 FPSOK
Пагинация1MB50ms✅ 60 FPSOK
Infinite Scroll2MB50ms✅ 60 FPSНужен сервер
Server-Side<1MB50ms✅ 60 FPS✅ Быстрый

На практике: 100,000 строк таблицы

// React с ag-Grid (профессиональная таблица)
import { AgGridReact } from 'ag-grid-react';
import 'ag-grid-community/styles/ag-grid.css';
import 'ag-grid-community/styles/ag-theme-quartz.css';

function DataGrid() {
    const rowData = Array(100000).fill(null).map((_, i) => ({
        id: i,
        name: `User ${i}`,
        email: `user${i}@example.com`,
        salary: Math.random() * 100000
    }));
    
    const columnDefs = [
        { field: 'id', width: 100 },
        { field: 'name', width: 150 },
        { field: 'email', width: 200 },
        { field: 'salary', width: 120 }
    ];
    
    return (
        <div className="ag-theme-quartz" style={{ height: '100vh', width: '100%' }}>
            <AgGridReact
                rowData={rowData}
                columnDefs={columnDefs}
                pagination={true}
                paginationPageSize={50}
                virtualizationPageSize={50}
            />
        </div>
    );
}

Чеклист для работы с большими списками

  • Виртуальная прокрутка (react-window, react-virtualized)
  • Пагинация на сервере
  • Ленивая загрузка (infinite scroll)
  • Фильтрация на бэкенде
  • Кеширование результатов
  • Дебаунс для поиска
  • Оптимизированные key'и в React
  • Мемоизация компонентов (React.memo)
  • Lazy loading изображений

Вывод

100,000 строк — это НЕ проблема для современного веба, если:

  • Используешь виртуализацию
  • Имеешь хорошую пагинацию/infinite scroll
  • Фильтруешь на сервере
  • Оптимизируешь компоненты

Без оптимизации — да, браузер зависнет. С оптимизацией — будет летать!