Гид по технологиям

Бесконечная прокрутка в React: руководство и лучшие практики

7 min read Frontend Обновлено 02 Jan 2026
Бесконечная прокрутка в React — руководство
Бесконечная прокрутка в React — руководство

Ноутбук, подключённый к внешнему экрану на деревянном столе с лампой и растением

Бывали ли у вас сайты или приложения, которые подгружают и отображают новый контент по мере прокрутки страницы? Это и называется бесконечной прокруткой (infinite scroll).

Бесконечная прокрутка — популярная техника для просмотра большого объёма контента. Она может сделать навигацию плавнее и удобнее, особенно на мобильных устройствах, но также влечёт за собой ряд ограничений и рисков (производительность, доступность, SEO, тестирование).

Что такое бесконечная прокрутка и когда её применять

Коротко: бесконечная прокрутка загружает дополнительные элементы по мере прокрутки страницы; обычно это реализуется через обработчики событий прокрутки, готовые компоненты или API браузера (Intersection Observer).

Когда использовать:

  • Фиды контента (лентa соцсетей, маркетплейсы с большим числом карточек).
  • Сценарии, где пользователь ожидает «непрерывного» просмотра.

Когда не использовать:

  • Если пользователю важна точная навигация по страницам (SEO-страницы поиска, каталоги с фильтрами, где нужен сохранный URL и пагинация).
  • Если высокий риск, что пользователь пропустит важный контент (контент, требующий обязательного просмотра).

Важно: альтернативы — классическая пагинация, кнопка «Загрузить ещё» или гибрид «пагинация + бесконечная прокрутка».

Основные способы реализации в React

Коротко описанные варианты:

  • Библиотеки (react-infinite-scroll-component и аналоги) — быстрый путь с готовыми обработчиками состояния.
  • Хуки и нативные события scroll — простая реализация, но требует debounce/throttle и аккуратной работы с состоянием.
  • Intersection Observer — современный и предпочтительный способ: экономный по CPU, не зависит от частоты событий scroll.

Примеры из практики: react-infinite-scroll-component

Удобная библиотека для быстрого старта. Ниже — пример с класс-компонентом.

npm install react-infinite-scroll-component --save
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

Объяснение ключевых пропсов:

  • dataLength — длина текущего массива элементов; библиотека использует это для принятия решения, нужно ли запускать next.
  • next — функция, загружающая следующую порцию (обычно вызывает API).
  • hasMore — флаг наличия следующей страницы.
  • loader / endMessage — отображаемые блоки при загрузке и при окончании данных.

Советы при использовании библиотек:

  • Всегда добавляйте обработку ошибок и состояние загрузки (isLoading), чтобы избежать повторных вызовов.
  • Применяйте throttle/debounce для fetch-функций, если вы смешиваете с нативными scroll-событиями.
  • Контролируйте уникальные ключи элементов для корректного рендера.

React-приложение с бесконечной прокруткой, использующее сторонние пакеты

Реализация с хуками и нативными событиями scroll

Ниже — пример на функциональном компоненте с useState и useEffect, как в исходном материале.

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 вызывается очень часто — нужно использовать throttle/debounce.
  • Состояние page должно обновляться через функциональный set (setPage(p => p + 1)), чтобы избежать проблем с замыканиями.
  • Для серверной пагинации лучше передавать номер страницы или курсор.

Intersection Observer — рекомендуемый нативный способ

Intersection Observer отслеживает видимость «маяка» внизу списка и подгружает данные только тогда, когда маяк попадает в область видимости. Это экономит ресурсы и точнее, чем постоянный опрос позиции прокрутки.

Пример (фрагмент кода):

import React, { useRef, useState, useEffect } from 'react'

function InfiniteList({ fetchPage }) {
  const [items, setItems] = useState([])
  const [page, setPage] = useState(1)
  const [hasMore, setHasMore] = useState(true)
  const loader = useRef(null)

  useEffect(() => {
    const observer = new IntersectionObserver(entries => {
      if (entries[0].isIntersecting && hasMore) {
        setPage(p => p + 1)
      }
    }, { root: null, rootMargin: '200px', threshold: 0.1 })

    if (loader.current) observer.observe(loader.current)
    return () => observer.disconnect()
  }, [hasMore])

  useEffect(() => {
    let cancelled = false
    async function load() {
      const result = await fetchPage(page)
      if (cancelled) return
      setItems(prev => [...prev, ...result.items])
      setHasMore(result.hasMore)
    }
    load()
    return () => { cancelled = true }
  }, [page, fetchPage])

  return (
    
{items.map((item, i) =>
{item}
)}
) } export default InfiniteList

Преимущества Intersection Observer:

  • Меньше событий, лучше для батареи и CPU.
  • Можно задавать rootMargin, чтобы заранее начать загрузку.
  • Работает в большинстве современных браузеров; для старых браузеров — полифилл.

Дизайн API для бесконечной прокрутки (сервер + клиент)

Рекомендации по API:

  • Предпочитайте курсорную пагинацию (cursor/continuation token) для стабильности при частых изменениях данных.
  • Если используете offset-based пагинацию, внимательно следите за изменениями коллекции (удаление/добавление) — оффсеты могут «перепрыгивать» элементы.
  • Ответ API должен содержать массив элементов и флаг/токен следующей страницы (hasMore или nextCursor).
  • Пагинация должна возвращать предсказуемое количество элементов (pageSize).

Пример формата ответа:

  • { items: […], nextCursor: “abc123”, hasMore: true }

Доступность (a11y) и SEO

Доступность:

  • Добавьте фокусируемый «маяк» и aria-live-регионы для уведомления пользователей экранных читалок о новой подгрузке контента.
  • Обеспечьте клавиатурную навигацию: пользователь должен иметь возможность добраться до конца списка с клавиатуры и активировать загрузку.
  • Предусмотрите «паузы» или кнопку «Загрузить ещё» для пользователей, которым нужна контроль над потоком контента.

SEO:

  • Серверная страница или отдельные URL для секций обеспечат индексируемость. Бесконечная прокрутка часто мешает SEO — используйте прогрессивное улучшение: базовая пагинация на сервере + client-side infinite scroll.
  • Обновляйте URL (history.pushState) при подгрузке важных «страничных» состояний, только если это логично для пользователя.

Производительность и удержание памяти

Хуки и классы могут накапливать тысячи DOM-элементов. Советы:

  • Используйте виртуализацию (react-window, react-virtualized) для больших списков.
  • Ограничьте количество одновременно отрисованных элементов (windowing).
  • Очистка неиспользуемых данных: при прокрутке вверх/вниз можно удалять старые элементы из состояния и подгружать заново при необходимости.
  • Используйте ключи и memoization, чтобы минимизировать перерендеры.

Когда бесконечная прокрутка не сработает — типичные ошибки и контрпримеры

Примеры провальных сценариев:

  • Каталог товаров, где пользователю нужно сравнивать товары по страницам и делиться конкретной страницей — бесконечная прокрутка усложнит навигацию и SEO.
  • Страницы со смешанным контентом (новости+реклама): реклама может «провалиться» в середину пользовательской ленты, что снизит CTR и приведёт к путанице.
  • Сильно динамичный контент (частые добавления/удаления): оффсетная пагинация будет нестабильной.

Альтернативы:

  • Кнопка «Показать ещё» — лучший выбор для сохранения контроля и простоты тестирования.
  • Классическая пагинация — чёткие URL, простота кеширования и SEO.

Критерии приёмки и тестовые сценарии

Критерии приёмки (acceptance):

  • При достижении (или приближении к) конца списка вызывается загрузка следующей порции данных.
  • Нет дублирования элементов после нескольких последовательных загрузок.
  • Обработка ошибок: при неудаче показывается сообщение и доступна попытка повтора.
  • Поток данных останавливается при hasMore=false и показывается сообщение об окончании.
  • Поддержка клавиатуры и уведомлений для скринридеров о новой подгрузке.

Тестовые сценарии:

  • Фронтенд: имитация медленного соединения — элементы загружаются корректно, loader отображается.
  • Нагрузочное тестирование: симуляция одновременных пользователей и множественных подрядных загрузок — отсутствие утечек памяти.
  • SEO: проверка доступности контента при отключённом JS (если применимо) или корректная серверная пагинация.

Роли и чек-листы перед релизом

Разработчик:

  • Реализовать debounce/throttle и обработку ошибок.
  • Добавить логирование загрузок и метрики (latency, error rate).
  • Имплементировать очистку слушателей и отмену запросов при анмаунте.

QA:

  • Проверить edge-cases: пустой ответ, ошибки сети, быстрые пролистывания.
  • Проверить на мобильных устройствах и старых браузерах.

Продукт/PM:

  • Принять решение: бесконечная прокрутка, кнопка «Загрузить ещё» или пагинация.
  • Оценить влияние на бизнес-метрики: удержание, CTR, конверсии.

Инцидентный план: быстрый откат

Если после релиза поведение пагинации вызывает проблемы (замедления, дубли, ошибки):

  1. Отключить клиентский бесконечный скролл (feature flag) — вернуть кнопку «Загрузить ещё» или пагинацию.
  2. Откатить изменения на фронтенде, развернуть горячий фикс.
  3. Проверить логи API и аналитики: метрики ошибок и времени ответа.
  4. Проанализировать причину (память, запросы, гонки) и исправить в ветке разработки.

Чек-лист для производительности (короткий)

  • Использовать виртуализацию при большом количестве элементов.
  • Ограничивать глубину истории состояния (не хранить вечный массив в памяти без лимитов).
  • Сжимать возможные payloads на беке — возвращать только необходимые поля.
  • Установить разумный pageSize (не слишком большой, не слишком маленький).

Минимальная методология внедрения (шаги)

  1. Решите, нужен ли бесконечный скролл для задачи.
  2. Выберите подход: библиотека / Intersection Observer / кнопка «Загрузить ещё».
  3. Спроектируйте API (cursor vs offset).
  4. Реализуйте прототип и протестируйте на мобильных и десктопных устройствах.
  5. Добавьте метрики и проверяйте поведение в продакшене с feature flag.

Краткое резюме

  • Бесконечная прокрутка повышает удобство просмотра, но требует дополнительной работы по доступности, SEO и производительности.
  • Intersection Observer — предпочтительная нативная техника; для больших списков обязательно используйте виртуализацию.
  • Рассматривайте альтернативы (кнопка «Загрузить ещё», пагинация) в зависимости от бизнес-требований.

Примечание: перед массовым внедрением выполните A/B-тестирование, чтобы измерить влияние на поведение пользователей.

Часто задаваемые вопросы

Вопрос: Что лучше — бесконечная прокрутка или пагинация?

Ответ: Зависит от сценария. Для лент и feed’ов бесконечная прокрутка чаще удобнее. Для товаров и страниц, где важен URL и индексируемость — пагинация.

Вопрос: Как избежать утечек памяти при бесконечной прокрутке?

Ответ: Используйте виртуализацию, ограничивайте размер кеша в клиенте и очищайте неиспользуемые слушатели/запросы.

Вопрос: Нужно ли обновлять URL при подгрузке следующей порции?

Ответ: Только если новые порции соответствуют логическому состоянию, к которому пользователь может захотеть вернуться или поделиться ссылкой. В противном случае не стоит.

Экран приложения React с бесконечной прокруткой, реализованной встроенными возможностями

Поделиться: X/Twitter Facebook LinkedIn Telegram
Автор
Редакция

Похожие материалы

Мониторинг процессов Windows с Kiwi
Windows

Мониторинг процессов Windows с Kiwi

Как открыть Командную строку в Windows
Windows

Как открыть Командную строку в Windows

Как открыть Sticky Notes в Windows 11
Windows

Как открыть Sticky Notes в Windows 11

Исправить: «Жёсткий диск не установлен» в Windows
Техподдержка

Исправить: «Жёсткий диск не установлен» в Windows

Как отключить сохранение разговоров в ChatGPT
Конфиденциальность

Как отключить сохранение разговоров в ChatGPT

Устранение лагов YouTube в Chrome
браузер

Устранение лагов YouTube в Chrome