> Как реализовать бесконечный скролл (JavaScript)
Уровень: senior · Роль: frontend · Категория: Технические вопросы
Компании: TYMY
Стек: JavaScript
> Пример ответа
Короткий ответ
Бесконечный скролл реализуется через Intersection Observer API или обработчик scroll с throttling. Основная идея - отслеживать, когда пользователь достигает конца списка, и подгружать следующую порцию данных. Ключевые моменты: управление состоянием загрузки, предотвращение дублирующих запросов, корректная обработка ошибок и очистка слушателей при размонтировании компонента.
Подробное объяснение
Бесконечный скролл - это паттерн пагинации, при котором новый контент загружается автоматически, когда пользователь прокручивает страницу до конца. Основные подходы:
-
Intersection Observer API - современный, производительный способ. Создаётся наблюдатель за элементом-триггером (обычно последний элемент списка или специальный sentinel). Когда триггер пересекает viewport, вызывается callback для загрузки данных. Observer автоматически оптимизирует проверки, не нагружая main thread.
-
Scroll event + throttling - классический подход. Навешивается обработчик scroll на контейнер или window, проверяется
scrollTop + clientHeight >= scrollHeight. Для избежания частых вызовов используется throttle (например, 100-200ms). Минус - может вызывать layout thrashing при частых чтениях геометрии. -
Virtual scrolling - для очень больших списков (тысячи элементов) используется виртуализация: рендерятся только видимые элементы, а DOM-дерево не перегружается. Бесконечный скролл в этом случае комбинируется с подгрузкой новых данных при приближении к концу буфера.
Ключевые аспекты реализации:
- Управление состоянием: флаги
isLoading,hasMore,error. Предотвращение повторных запросов, пока текущий не завершён. - Обработка ошибок: при ошибке загрузки показывать сообщение и кнопку "Повторить", не блокируя дальнейший скролл.
- Очистка ресурсов: отключение observer или удаление обработчика при unmount компонента.
- Debounce/Throttle: для scroll-подхода - throttle, для поиска - debounce.
- Оптимизация рендера: мемоизация списка, использование
React.memoилиkeyдля эффективного обновления.
На практике
В современных React-приложениях предпочтительнее Intersection Observer. Типичная реализация:
- Создаётся ref для sentinel-элемента (обычно div в конце списка).
- В
useEffectинициализируетсяIntersectionObserverсrootMargin: '100px'(подгрузка за 100px до конца). - При пересечении вызывается функция загрузки, если
hasMoreи неisLoading. - После успешной загрузки данные добавляются в стейт,
hasMoreобновляется на основе ответа сервера. - При ошибке устанавливается флаг
error, показывается UI для повторной попытки. - В cleanup observer отключается.
Для scroll-подхода важно использовать passive: true и throttle из lodash или собственный. Также нужно учитывать, что scroll-событие может не сработать при быстрой прокрутке - Intersection Observer лишён этого недостатка.
Пример кода
JAVASCRIPTimport { useRef, useEffect, useState, useCallback } from 'react';function InfiniteScrollList() {const [items, setItems] = useState([]);const [isLoading, setIsLoading] = useState(false);const [hasMore, setHasMore] = useState(true);const [error, setError] = useState(null);const sentinelRef = useRef(null);const loadMore = useCallback(async () => {if (isLoading || !hasMore) return;setIsLoading(true);setError(null);try {const newItems = await fetchItems(items.length, 20);setItems(prev => [...prev, ...newItems]);setHasMore(newItems.length === 20);} catch (err) {setError('Ошибка загрузки');} finally {setIsLoading(false);}}, [isLoading, hasMore, items.length]);useEffect(() => {const observer = new IntersectionObserver(([entry]) => {if (entry.isIntersecting) loadMore();},{ rootMargin: '100px' });if (sentinelRef.current) observer.observe(sentinelRef.current);return () => observer.disconnect();}, [loadMore]);return (<div>{items.map((item, idx) => (<div key={item.id}>{item.name}</div>))}<div ref={sentinelRef} style={{ height: 1 }} />{isLoading && <div>Загрузка...</div>}{error && <button onClick={loadMore}>Повторить</button>}</div>);}
Как отвечать на собеседовании
Начни с выбора основного подхода - Intersection Observer, объясни почему он предпочтительнее scroll-события (производительность, простота). Упомяни управление состоянием (isLoading, hasMore, error) и важность очистки. Для senior-уровня добавь про виртуализацию для больших списков, про обработку race conditions (если пользователь быстро скроллит), про оптимизацию с useCallback/useMemo. Если спросят про scroll - расскажи про throttle и passive listener. Покажи понимание trade-off между подходами.
Что проверяет интервьюер
- Понимание современных API браузера (Intersection Observer).
- Умение управлять асинхронными состояниями (загрузка, ошибка, завершение).
- Знание принципов оптимизации производительности (throttle, observer, очистка).
- Способность предотвращать баги (дублирующие запросы, утечки памяти).
- Понимание UX-нюансов (prefetch за границей экрана, обработка ошибок).
Типичные ошибки
- Отсутствие проверки
isLoading- отправка множества запросов при быстром скролле. - Забытая очистка observer или listener - утечка памяти и баги после unmount.
- Использование scroll-события без throttle - падение FPS.
- Неправильный rootMargin - подгрузка слишком поздно или слишком рано.
- Игнорирование ошибок - список зависает без возможности повтора.
- Отсутствие key или неправильные key в списке - перерендер всех элементов.
> Похожие задачи по frontend
Для чего нужен тип Symbol в JavaScript?
Как создать и выполнить промис через конструктор в JavaScript
Как избежать конкатенации чисел как строк в функции суммы аргументов в JavaScript?
Как реализовать функцию memo для кеширования результатов вызовов по аргументам в JavaScript
> ГОТОВЫ К СЛЕДУЮЩЕМУ СОБЕСЕДОВАНИЮ?
Запустите тренировочную сессию с ИИ и получите детальную обратную связь, чтобы увереннее проходить реальные интервью