Как отобразить большой список из 10 тысяч элементов без лагов
Что проверяют
Знаете ли вы, что тормозит не массив из 10 000 объектов, а 10 000 узлов в DOM и их перерисовка при каждом обновлении. Ждут слово «виртуализация» и понимание её цены. Следом спрашивают, что делать со строками разной высоты.
Как отвечать
- виртуализация: в DOM рендерятся только видимые строки плюс небольшой запас (overscan), а полная высота контейнера имитируется, чтобы скроллбар вёл себя честно. На экране десятки узлов вместо тысяч. Инструменты:
@tanstack/react-virtual,react-window,react-virtuoso, в Angularcdk-virtual-scroll-viewport, во Vuevue-virtual-scroller; - разная высота строк: при фиксированной позиция считается умножением, при переменной строки измеряют после рендера (
measureElementв TanStack Virtual) или берут библиотеку, которая делает это сама, как Virtuoso; content-visibility: auto: лёгкая альтернатива на CSS. Браузер пропускает отрисовку невидимых блоков, аcontain-intrinsic-sizeзадаёт им примерную высоту. DOM при этом остаётся полным, поэтому на 10 000 строк помогает, на 100 000 уже нет;- данные порциями: если все 10 000 сразу не нужны, пагинация или подгрузка через
IntersectionObserverна последней строке; - стоимость строки: стабильный
keyпо ID, строка вReact.memo, обработчики не пересоздаются на каждый рендер. Фильтрация и сортировка вuseMemo, поле фильтра черезuseDeferredValue, тяжёлые вычисления в Web Worker.
Скажите, как проверите результат: вкладка Performance в DevTools и React Profiler до и после, а не «на глаз стало быстрее».
Где спотыкаются
Называют виртуализацию и не знают её побочных эффектов. Поиск браузера по Ctrl+F не находит невидимые строки, а скринридеру нужны aria-setsize и aria-posinset у списка или aria-rowcount у таблицы, иначе он не понимает размер.
Ставят React.memo на каждую строку и считают задачу решённой. Ре-рендеров меньше, но 10 000 узлов в DOM никуда не делись.
Используют индекс как key в списке с сортировкой или удалением. Строки получают чужое состояние, а мемоизация перестаёт работать.