Как оптимизируешь рендер на React частичной выдачи карточек пользователю?
Рендеринг и производительность
«Частичная выдача карточек» — постраничная/порционная подгрузка (лента, каталог). Оптимизация складывается из работы с данными, DOM и отзывчивостью.
Данные: порциями и с кешем
- Курсорная пагинация вместо offset — не будет дублей и пропусков при изменении данных.
useInfiniteQuery(TanStack Query): дедупликация, кеш страниц, отмена, ретраи; при возврате назад список восстанавливается из кеша вместе с позицией.- Префетч следующей порции заранее — по IntersectionObserver с
rootMargin: '300px', а не по достижению низа.
DOM: не копить узлы
После десятка страниц в DOM тысячи карточек — память и тормоза скролла. Решение: виртуализация (react-virtuoso/react-window), рендерящая только видимое окно, с поддержкой бесконечного скролла из коробки.
Рендер: не перерисовывать всё
React.memoна карточке + стабильные props: колбэки через useCallback с id-аргументом, никаких инлайн-объектов/стилей.- Стабильные
key— id карточки, не индекс: иначе при подгрузке страниц состояние карточек путается. - Локальный state внутри карточки (раскрытие, лайк), чтобы не рендерить список целиком.
Отзывчивость и восприятие
useTransition/useDeferredValueдля фильтров над большим списком — ввод не подвисает.- Скелетоны фиксированной высоты и
aspect-ratioу изображений — не будет скачков layout (CLS). - Изображения:
loading="lazy", нужные размеры, WebP/AVIF; тяжёлые карточки —content-visibility: auto.
Проверка
Profiler (сколько карточек рендерится на действие), Performance (длинные задачи), метрики INP/CLS.
Коротко для собеседования
Данные — курсорная пагинация + useInfiniteQuery с префетчем по IntersectionObserver; DOM — виртуализация вместо накопления узлов; рендер — memo на карточке, стабильные props и key по id, локальный state внутри карточки; отзывчивость — useTransition для фильтров, скелетоны с фиксированной высотой и lazy-изображения. Проверять профайлером и Web Vitals.