> Как реализовать бесконечную ленту (infinite scroll) в веб-приложении (JavaScript)

Уровень: senior · Роль: frontend · Категория: Технические вопросы

Компании: YADRO

Стек: JavaScript

> Пример ответа

Короткий ответ

Infinite scroll реализуется через Intersection Observer API или событие scroll с троттлингом. При достижении триггерного элемента (обычно последнего в списке) отправляется запрос на сервер за следующей порцией данных, которые добавляются в DOM. Ключевые моменты: управление состоянием пагинации, обработка ошибок, предотвращение дублирования запросов и корректная работа с памятью при большом количестве элементов.

Подробное объяснение

Infinite scroll - это паттерн пагинации, где новый контент подгружается автоматически при прокрутке страницы вниз. Основные компоненты реализации:

  1. Детекция триггерной точки: используется Intersection Observer (современный подход) или scroll event с троттлингом. Observer наблюдает за элементом-маяком, расположенным в конце списка.

  2. Управление состоянием: храним текущую страницу, флаг загрузки, флаг окончания данных (hasMore), возможные ошибки. Это критично для предотвращения множественных запросов.

  3. Асинхронная загрузка: при срабатывании триггера отправляется запрос к API с параметрами пагинации (page, limit, cursor). После получения ответа данные добавляются к существующему списку.

  4. Обработка граничных случаев: пустой ответ (конец данных), ошибка сети, быстрая прокрутка (несколько срабатываний триггера), удаление элементов из середины списка.

  5. Оптимизация производительности: виртуализация списка (react-window, virtual-scroller) для больших наборов данных, debounce для scroll-событий, очистка observer при размонтировании.

На практике

В production-приложениях используют готовые решения или строят на основе Intersection Observer. Типичная архитектура:

  • Компонент-контейнер управляет состоянием и рендерит список
  • Компонент-маяк (sentinel) размещается после последнего элемента
  • Хук useInfiniteScroll инкапсулирует логику observer и вызовов API
  • Для cursor-based пагинации передаётся идентификатор последнего элемента
  • Обработка ошибок: показываем кнопку "Повторить" вместо автоматической загрузки
  • Для SEO и accessibility добавляем fallback в виде обычной пагинации

Пример кода

JAVASCRIPT
// Хук для infinite scroll
function useInfiniteScroll({ fetchMore, hasMore }) {
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const sentinelRef = useRef(null);
useEffect(() => {
if (!hasMore) return;
const observer = new IntersectionObserver(
async ([entry]) => {
if (entry.isIntersecting && !loading) {
setLoading(true);
setError(null);
try {
await fetchMore();
} catch (err) {
setError(err);
} finally {
setLoading(false);
}
}
},
{ rootMargin: '200px' }
);
const sentinel = sentinelRef.current;
if (sentinel) observer.observe(sentinel);
return () => {
if (sentinel) observer.unobserve(sentinel);
observer.disconnect();
};
}, [hasMore, loading, fetchMore]);
return { sentinelRef, loading, error };
}
// Использование
function Feed() {
const [items, setItems] = useState([]);
const [page, setPage] = useState(1);
const [hasMore, setHasMore] = useState(true);
const fetchMore = async () => {
const response = await api.getItems({ page, limit: 20 });
setItems(prev => [...prev, ...response.data]);
setHasMore(response.data.length === 20);
setPage(prev => prev + 1);
};
const { sentinelRef, loading, error } = useInfiniteScroll({
fetchMore,
hasMore
});
return (
<div>
{items.map(item => <Item key={item.id} data={item} />)}
{loading && <Spinner />}
{error && <button onClick={fetchMore}>Повторить</button>}
{hasMore && <div ref={sentinelRef} style={{ height: 1 }} />}
</div>
);
}

Как отвечать на собеседовании

Начни с ключевого механизма - Intersection Observer. Объясни, почему это лучше scroll-событий (производительность, точность). Затем перейди к управлению состоянием: как предотвращаешь race conditions, обрабатываешь ошибки, определяешь конец данных. Упомяни trade-off между offset и cursor пагинацией. Для senior-уровня добавь про виртуализацию, работу с памятью при тысячах элементов и SEO-аспекты.

Что проверяет интервьюер

  • Понимание асинхронных потоков и управления состоянием
  • Знание современных API браузера (Intersection Observer)
  • Умение проектировать компоненты с учётом граничных случаев
  • Навыки оптимизации производительности (троттлинг, виртуализация)
  • Понимание UX-паттернов и accessibility

Типичные ошибки

  • Использование scroll-события без троттлинга, что вызывает лаги
  • Отсутствие флага загрузки - множественные запросы при быстрой прокрутке
  • Неправильная очистка observer при размонтировании компонента
  • Игнорирование ошибок сети - пользователь видит вечную загрузку
  • Отсутствие fallback для пользователей с отключённым JS или screen reader
  • Утечка памяти при бесконечном добавлении элементов без виртуализации

> ГОТОВЫ К СЛЕДУЮЩЕМУ СОБЕСЕДОВАНИЮ?

Запустите тренировочную сессию с ИИ и получите детальную обратную связь, чтобы увереннее проходить реальные интервью