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

Виртуализация списков — это техника оптимизации отображения больших массивов данных в интерфейсе, позволяющая рендерить только те элементы, которые видны пользователю в данный момент, вместо полного списка. Atomico, как современная библиотека для создания веб-компонентов на базе Web Components и реактивного подхода, предоставляет гибкие возможности для реализации виртуализированных списков.


Принципы работы

Ключевая идея виртуализации состоит в управлении видимым диапазоном элементов. Вместо создания DOM-узлов для всех элементов списка, компонент хранит полный массив данных, но в DOM рендерятся только элементы, попадающие в область видимости. При прокрутке диапазон обновляется динамически.

Основные элементы виртуализации:

  • buffer — небольшое количество элементов за пределами видимой области для плавной прокрутки.
  • scroll container — элемент с фиксированной высотой, внутри которого происходит рендеринг видимых элементов.
  • placeholder / spacer — блоки, создающие эффект полной высоты списка без необходимости рендерить все элементы.

Реактивное состояние и Atomico

Atomico использует реактивные состояния через useState и useEffect, что идеально подходит для виртуализации. Пример структуры состояния:

import { c, useState, useEffect, useRef } from "atomico";

function VirtualList({ items, itemHeight, containerHeight }) {
    const [scrollTop, setScrollTop] = useState(0);
    const containerRef = useRef();

    const visibleCount = Math.ceil(containerHeight / itemHeight);
    const startIndex = Math.floor(scrollTop / itemHeight);
    const endIndex = Math.min(startIndex + visibleCount + 2, items.length);

    const visibleItems = items.slice(startIndex, endIndex);

    useEffect(() => {
        const handleScroll = () => setScrollTop(containerRef.current.scrollTop);
        const el = containerRef.current;
        el.addEventListener("scroll", handleScroll);
        return () => el.removeEventListener("scroll", handleScroll);
    }, []);

    return (
        <host>
            <div
                ref={containerRef}
                style={{
                    height: `${containerHeight}px`,
                    overflowY: "auto",
                    position: "relative"
                }}
            >
                <div style={{ height: `${items.length * itemHeight}px`, position: "relative" }}>
                    {visibleItems.map((item, index) => (
                        <div
                            style={{
                                position: "absolute",
                                top: `${(startIndex + index) * itemHeight}px`,
                                height: `${itemHeight}px`,
                                width: "100%"
                            }}
                        >
                            {item}
                        </div>
                    ))}
                </div>
            </div>
        </host>
    );
}

export const virtualList = c(VirtualList);

В этом примере:

  • containerRef отслеживает прокрутку.
  • scrollTop — текущее смещение, которое используется для расчета видимого диапазона.
  • Абсолютное позиционирование элементов позволяет создавать ощущение полного списка без рендеринга всех элементов.

Оптимизация производительности

Для списков с десятками тысяч элементов важно:

  1. Минимизировать количество ререндеров — использовать useMemo для вычисления видимых элементов.
  2. Использовать фиксированную высоту элементов, что упрощает вычисление позиции без измерений DOM.
  3. Буферизация — рендерить на 1–2 элемента больше за пределами видимой области для плавной прокрутки.
  4. Event throttling / debouncing при обработке scroll для снижения нагрузки.

Пример добавления буфера и мемоизации:

import { useMemo } from "atomico";

const buffer = 2;

const visibleItems = useMemo(() => {
    const start = Math.max(startIndex - buffer, 0);
    const end = Math.min(endIndex + buffer, items.length);
    return items.slice(start, end);
}, [startIndex, endIndex, items]);

Динамическая высота элементов

Если элементы имеют переменную высоту, стандартная виртуализация с абсолютным позиционированием не подходит. Для Atomico применим подход с хранением кумулятивной высоты:

  1. Создать массив с суммой высот всех элементов до текущего.
  2. Использовать этот массив для расчета top позиции каждого элемента.
  3. Обновлять массив при изменении высоты любого элемента.

Такой подход требует дополнительного состояния и рефов для отслеживания высоты, но сохраняет производительность при больших списках.


Комбинация с JSX и слотами

Atomico позволяет интегрировать виртуализированный список с слотами и пользовательскими компонентами:

<div style={{ height: "400px", overflowY: "auto" }}>
    <slot name="item" />
</div>

Это позволяет передавать шаблон рендеринга элемента извне, сохраняя реактивность и гибкость.


Применение в реальных проектах

Виртуализация списков полезна при:

  • Таблицах с тысячами строк.
  • Лентах новостей или соцсетей.
  • Каталогах товаров с динамическим фильтром.
  • Веб-приложениях с историей или журналами действий.

Atomico, благодаря реактивной модели и поддержке Web Components, позволяет создавать модульные и легко настраиваемые виртуализированные компоненты, которые интегрируются в любые проекты без зависимости от фреймворков вроде React или Vue.


Хорошее понимание принципов виртуализации и их реализация через Atomico дает возможность создавать высокопроизводительные интерфейсы даже при огромных объемах данных, сохраняя плавность и отзывчивость приложения.