Бесконечная прокрутка в React.js

Коротко о термине
Бесконечная прокрутка — паттерн загрузки данных по мере прокрутки страницы. Подход удобен для длинных лент, но усложняет навигацию, отслеживание прогресса и индексирование контента.
Реализация бесконечной прокрутки в React.js
Существует несколько подходов:
- Использовать готовую библиотеку (например, react-infinite-scroll-component). Она упрощает логику, но добавляет зависимость.
- Использовать встроенные методы React (lifecycle или хуки) и обработчик события scroll.
- Использовать IntersectionObserver для более точного и энергоэффективного отслеживания «подхода к концу» дом-элемента.
Ниже — перевод и адаптация примеров, советы по лучшим практикам, критерии приёмки и чек-листы для разработчиков, QA и менеджеров.
Использование react-infinite-scroll-component
Установка
Чтобы начать, установите пакет через npm:
npm install react-infinite-scroll-component --saveИмпорт и пример класса
После установки импортируйте компонент в свой React-компонент.
import React from 'react'
import InfiniteScroll from 'react-infinite-scroll-component'
class App extends React.Component {
constructor() {
super()
this.state = {
items: [],
hasMore: true
}
}
componentDidMount() {
this.fetchData(1)
}
fetchData = (page) => {
const newItems = []
for (let i = 0; i < 100; i++) {
newItems.push(i )
}
if (page === 100) {
this.setState({ hasMore: false })
}
this.setState({ items: [...this.state.items, ...newItems] })
}
render() {
return (
Infinite Scroll
Loading...}
endMessage={
Yay! You have seen it all
}
>
{this.state.items.map((item, index) => (
{item}
))}
)
}
}
export default AppЭтот пример создаёт state с пустым массивом items и флагом hasMore. Метод fetchData генерирует dummy-данные и добавляет их в состояние. Когда достигнут последний «страничный» номер, флаг hasMore переводится в false, и компонент перестаёт запрашивать новые данные.
Параметры и поведение
- dataLength — длина массива данных. Компонент использует это значение, чтобы понять, что появились новые элементы.
- next — функция, вызываемая для подгрузки следующей порции данных.
- hasMore — флаг, указывающий, есть ли ещё данные.
- loader — элемент-индикатор загрузки.
- endMessage — сообщение при достижении конца.
Важно: в реальном приложении fetchData должен делать сетевой запрос к API, обрабатывать ошибки и показывать индикатор загрузки.

Использование встроенных методов и хуков
Можно реализовать бесконечную прокрутку с помощью хуков useState и useEffect, а также обработчика scroll.
import React, {useState, useEffect} from 'react'
function App() {
const [items, setItems] = useState([])
const [hasMore, setHasMore] = useState(true)
const [page, setPage] = useState(1)
useEffect(() => {
fetchData(page)
}, [page])
const fetchData = (page) => {
const newItems = []
for (let i = 0; i < 100; i++) {
newItems.push(i)
}
if (page === 100) {
setHasMore(false)
}
setItems([...items, ...newItems])
}
const onScroll = () => {
const scrollTop = document.documentElement.scrollTop
const scrollHeight = document.documentElement.scrollHeight
const clientHeight = document.documentElement.clientHeight
if (scrollTop + clientHeight >= scrollHeight) {
setPage(page + 1)
}
}
useEffect(() => {
window.addEventListener('scroll', onScroll)
return () => window.removeEventListener('scroll', onScroll)
}, [items])
return (
{items.map((item, index) => (
{item}
))}
)
}
export default AppЭтот подход прост, но у него есть недостатки: слушатель scroll может срабатывать очень часто и нагружать основной поток. Используйте debounce/throttle, чтобы снизить частоту вызовов, и не забывайте отписываться от событий.

IntersectionObserver: современный и эффективный способ
IntersectionObserver отслеживает пересечение DOM-элемента с viewport и работает более экономно, чем глобальный обработчик scroll. Подойдёт для загрузки следующей пачки элементов, когда «сентинел» (нижний маркер) появляется в зоне видимости.
Пример с хуками:
import React, { useRef, useState, useEffect } from 'react'
function InfiniteWithObserver({ loadMore }) {
const [items, setItems] = useState([])
const [page, setPage] = useState(1)
const [hasMore, setHasMore] = useState(true)
const sentinelRef = useRef(null)
useEffect(() => {
// загрузка первой страницы
loadPage(page)
}, [])
useEffect(() => {
if (!sentinelRef.current) return
const observer = new IntersectionObserver(entries => {
entries.forEach(entry => {
if (entry.isIntersecting && hasMore) {
setPage(p => p + 1)
}
})
})
observer.observe(sentinelRef.current)
return () => observer.disconnect()
}, [hasMore])
useEffect(() => {
if (page === 1) return
loadPage(page)
}, [page])
function loadPage(p) {
// loadMore должен возвращать Promise с массивом элементов
loadMore(p).then(newItems => {
if (!newItems || newItems.length === 0) setHasMore(false)
setItems(prev => [...prev, ...newItems])
}).catch(() => setHasMore(false))
}
return (
{items.map((it, i) => {it})}
{hasMore && }
)
}
export default InfiniteWithObserverПреимущества: меньше работы в основном потоке, лучшая батареечная эффективность и более точный триггер загрузки.
Плюсы и минусы бесконечной прокрутки
Плюсы:
- Плавное взаимодействие и ощущение «безостановочного» просмотра.
- Меньше кликов и перезагрузок страниц.
- Хорошо подходит для лент, соцсетей и галерей.
Минусы:
- Потеря ориентации: пользователи могут не понять, где нашли конкретную запись.
- Сложности с закладками и прямыми ссылками на элементы далеко вниз в ленте.
- Проблемы с SEO и индексированием (поисковым ботам может быть сложнее просмотреть весь контент).
- Рост потребления памяти в браузере при отсутствии виртуализации.
Важно: комбинируйте бесконечную прокрутку с возможностью поиска, фильтрации и «показать ещё» для сохранения контроля.
Когда бесконечная прокрутка не подходит
- Если пользователи ожидают явную навигацию по страницам (например, каталоги товаров с чёткими страницами и URL).
- Когда важна возможность ссылаться на конкретное место в списке.
- Если требуется строгая SEO-оптимизация для каждой порции контента.
В таких сценариях лучше использовать пагинацию или гибридный подход: пагинация для значимых разделов и бесконечная прокрутка для вспомогательных лент.
Альтернативы и гибриды
- Кнопка «Показать ещё» — простая и предсказуемая альтернатива. Пользователь контролирует загрузку.
- Пагинация — даёт стабильные URL и предсказуемость; подходит для каталогов товаров.
- Гибрид: бесконечная прокрутка внутри страницы результатов с возможностью «загрузить всё».
Лучшие практики и эвристики
- Загружайте данные порциями (page size) и отдавайте пользователю индикатор загрузки.
- Используйте debounce/throttle для обработчиков scroll.
- При большом количестве элементов применяйте виртуализацию (react-window, react-virtualized).
- Сохраняйте позицию прокрутки при навигации назад (history state или session storage).
- Добавьте явную кнопку «Наверх» и «Показать ещё» для мобильных пользователей.
- Обрабатывайте ошибки сети и показывайте повторную попытку.
Доступность (A11y) и SEO
- Для доступности: обеспечьте фокусируемые элементы, доступные метки и возможность клавиатурной навигации.
- Экранные читалки: уведомляйте пользователя о динамически добавленных элементах с помощью aria-live.
- SEO: если контент важен для индексации, обеспечьте доступ к нему через пагинацию, sitemap или серверный рендеринг (SSR). Также можно использовать «progressive enhancement»: базовая пагинация + динамическая подгрузка для улучшенного UX.
Важно: не полагайтесь только на клиентский JavaScript для контента, который должен индексироваться.
Производительность и оптимизация
- Виртуализация: держите в DOM только видимые элементы.
- Lazy-loading изображений и медиаконтента.
- Кэширование страниц и результатов API.
- Ограничивайте объём данных в каждом запросе и используйте сжатие (gzip/brotli).
- Следите за утечками памяти: удаляйте обработчики и референсы.
Критерии приёмки
- Стандартный сценарий: при прокрутке вниз подгружается следующая порция данных без дублирования и потерь.
- Граница: при достижении конца списка отображается корректное сообщение «конец».
- Ошибки сети: при сбое показывается уведомление и кнопка «Повторить».
- Доступность: aria-live уведомляет о добавлении контента; клавиатурная навигация не ломается.
- Производительность: FPS не падает заметно при прокрутке; память контролируется.
Чек-листы ролей
Разработчик:
- Реализовать подгрузку и обработку ошибок.
- Добавить debounce/IntersectionObserver.
- Реализовать виртуализацию при большом списке.
- Сохранить позицию при навигации.
QA:
- Проверить сценарии подгрузки, дублирование и конец списка.
- Проверить восстановление после ошибки сети.
- Проверить доступность с клавиатуры и screen reader.
Продуктовый менеджер:
- Утвердить, где нужен infinite scroll, а где — пагинация.
- Определить допустимый latency и размер порций.
- Принять критерии приёмки.
План отката (инцидентный runbook)
- Зафиксировать время и симптомы (много дублированных элементов, утечки памяти, падения).
- Откатить фронтенд к версии без бесконечной прокрутки или с выключенным feature flag.
- Если причины на бэкенде — откатить API или лимитировать объём отдаваемых данных.
- Включить глубокое логирование и мониторинг (метрики latency, error rate, memory).
- Провести постмортем и исправить root cause.
Тестирование: тест-кейсы и приёмка
- TC1: Подгрузка следующей страницы при прокрутке до низа.
- TC2: Обработка пустой страницы (hasMore=false).
- TC3: Повторная попытка при ошибке сети.
- TC4: Нормальное поведение при очень медленном соединении.
- TC5: Виртуализация: проверка, что в DOM не держатся все элементы.
Сравнение: бесконечная прокрутка vs пагинация
| Критерий | Бесконечная прокрутка | Пагинация |
|---|---|---|
| UX для ленты | Отличный — плавный просмотр | Менее плавный, но предсказуемый |
| Ссылка на конкретный элемент | Сложно | Легко (URL) |
| SEO | Требует доработок | Хорошо индексируется |
| Контроль пользователя | Меньше контроля | Больше контроля |
| Реализация | Средняя сложность | Простая |
Методика внедрения (микро-план)
- Прототип: реализуйте на отдельном фиче‑брэнче с feature flag.
- Тестирование: функциональные и нагрузочные тесты.
- Релиз в Canary: ограничьте процент пользователей.
- Мониторинг: latency, errors, memory, engagement.
- Раскатка: при стабильности — включение для всех.
Резюме
Бесконечная прокрутка даёт плавный UX для лент и страниц с большим количеством контента. При внедрении важно учитывать доступность, SEO, производительность и сценарии отката. Выбор между библиотекой, хуками и IntersectionObserver зависит от требований к производительности и контролю над поведением.
Важно: в критичных с точки зрения SEO и навигации местах отдавайте приоритет пагинации или комбинируйте подходы.
Похожие материалы
Несколько аккаунтов Skype: Multi Skype Launcher
Журнал для работы: повысить продуктивность
Персональные звуки уведомлений на Android
Скачивание шоу Hulu для офлайн‑просмотра
Microsoft Start: персонализированная новостная лента