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

Пагинация в React: реализация на клиенте с хуками и лучшие практики

6 min read Разработка Обновлено 30 Dec 2025
Пагинация в React: хуки и лучшие практики
Пагинация в React: хуки и лучшие практики

Ноутбук с кодом JavaScript на экране

Веб-приложения управляют большими объёмами данных: каталоги товаров, поисковые результаты, ленты действий. Выводить всё в одну страницу — значит получить медленную загрузку и плохой пользовательский опыт. Пагинация решает эту проблему, разбивая данные на удобные страницы.

Пагинация бывает двух больших подходов:

  • клиентская пагинация — данные подгружены в браузер и разбиваются на страницы на клиенте;
  • серверная пагинация — сервер возвращает только один фрагмент данных (страницу) по запросу.

В этой статье мы реализуем клиентскую постраничную навигацию в React с использованием хуков и обсудим альтернативы, тесты и проверки.

Когда подойдёт клиентская пагинация

Client-side подходит, когда общий объём данных относительно невелик и может быть загружен целиком без проблем с памятью или трафиком. Примеры:

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

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

Открытый блокнот с пустыми страницами

Подходы к клиентской пагинации

Основные варианты разбивки данных на клиенте:

  • Пагинация по страницам (page-based). Данные делятся на фиксированные страницы. Пользователь выбирает номер страницы либо кликает Prev/Next.
  • Ленивый рендеринг и виртуализация. Отрисовываем в DOM только видимую часть списка, полезно для длинных списков.
  • Бесконечная прокрутка (infinite scroll). Загружаем следующую порцию при скролле вниз — удобно для лент, но сложнее для навигации и доступа к конкретным позициям.

Каждый подход имеет свои плюсы и минусы. Ниже — практическая реализация page-based пагинации.

Минимальный компонент пагинации на React с хуками

Задача: получить набор задач todo из публичного API jsonplaceholder, разбить на страницы и отрисовать навигацию с Prev / Next и номерами страниц.

Создаём файл компонента, например src/components/Pagination.jsx.

Пример рабочего компонента с комментариями и улучшениями по доступности и производительности:

import React, { useEffect, useMemo, useState } from 'react'
import './style.component.css'

function Pagination() {
  const [data, setData] = useState([])
  const [currentPage, setCurrentPage] = useState(1)
  const [itemsPerPage, setItemsPerPage] = useState(5)

  // сколько номеров страниц показывать в навигации одновременно
  const [pageNumberLimit] = useState(5)
  const [maxPageNumberLimit, setMaxPageNumberLimit] = useState(5)
  const [minPageNumberLimit, setMinPageNumberLimit] = useState(0)

  // загрузка данных один раз при монтировании
  useEffect(() => {
    let mounted = true
    fetch('https://jsonplaceholder.typicode.com/todos')
      .then(res => res.json())
      .then(json => {
        if (mounted) setData(json)
      })
      .catch(err => console.error('fetch error', err))
    return () => { mounted = false }
  }, [])

  // вычисляем массив номеров страниц и элементы текущей страницы
  const pages = useMemo(() => {
    const total = Math.ceil(data.length / itemsPerPage)
    return Array.from({ length: total }, (_, i) => i + 1)
  }, [data.length, itemsPerPage])

  const indexOfLastItem = currentPage * itemsPerPage
  const indexOfFirstItem = indexOfLastItem - itemsPerPage
  const pageItems = data.slice(indexOfFirstItem, indexOfLastItem)

  // отрисовка списка
  const displayData = items => (
    
    {items.length === 0 ? (
  • Нет данных для отображения
  • ) : ( items.map((todo, idx) =>
  • {todo.title}
  • ) )}
) const handleClick = event => { const page = Number(event.target.id) setCurrentPage(page) } const renderPageNumbers = pages.map(number => { if (number <= maxPageNumberLimit && number > minPageNumberLimit) { return (
  • { if (e.key === 'Enter') handleClick(e) }} > {number}
  • ) } else { return null } }) const handleNextbtn = () => { setCurrentPage(prev => prev + 1) if (currentPage + 1 > maxPageNumberLimit) { setMaxPageNumberLimit(prev => prev + pageNumberLimit) setMinPageNumberLimit(prev => prev + pageNumberLimit) } } const handlePrevbtn = () => { setCurrentPage(prev => prev - 1) if ((currentPage - 1) % pageNumberLimit === 0) { setMaxPageNumberLimit(prev => prev - pageNumberLimit) setMinPageNumberLimit(prev => prev - pageNumberLimit) } } return ( <>

    Компонент пагинации

    {displayData(pageItems)}
    • {renderPageNumbers}
    ) } export default Pagination

    Важно: в коде использованы общие улучшения — useMemo для вычисления страниц, aria-атрибуты и обработчики клавиатуры для доступности. Также рекомендуется добавить обработку ошибок и состояние загрузки.

    Пояснение ключевых шагов

    1. Получение данных через fetch в useEffect и сохранение в state.

    2. Вычисление общего числа страниц: ceil(totalItems / itemsPerPage).

    3. Выбор части массива для текущей страницы: slice(indexOfFirstItem, indexOfLastItem).

    4. Отрисовка списка pageItems вместо полного массива data.

    5. Навигация: номера страниц, Prev и Next, управление видимым диапазоном номеров.

    Улучшения и проверенные приёмы

    • Кэширование данных: если данные статичны, кэшируйте результат fetch в локальном хранилище или в память, чтобы не перезапрашивать при переходах.
    • Виртуализация (react-window / react-virtualized): если отображаемый список может содержать сотни или тысячи DOM-элементов, используйте виртуализацию вместо традиционной пагинации для гладкого скролла.
    • Пагинация на сервере: для больших данных предпочтительна серверная пагинация или курсорная пагинация, чтобы уменьшить объём передаваемых данных.
    • Показать всего N страниц с навигацией вперед/назад: полезно для длинных наборов страниц.
    • Поддержка URL-параметров: синхронизируйте currentPage с query string, чтобы можно было делиться ссылками и делать back/forward в браузере.

    Альтернативные подходы и когда они уместны

    • Серверная пагинация (page-based): сервер возвращает нужную страницу. Уместно при больших объёмах и при необходимости актуальных данных.
    • Курсорная пагинация (cursor-based): стабильно при изменяющихся данных, если важна консистентность при параллельных изменениях.
    • Infinite scroll: хорош для лент, но плох для доступа к конкретной позиции и истории браузера.
    • Комбинированный подход: серверно-страничный API + клиентская буферизация нескольких страниц для быстрой навигации.

    Аксессибельность и i18n

    • Кнопки должны быть фокусируемыми и управляемы с клавиатуры.
    • Используйте aria-label и aria-live для уведомления о смене контента.
    • Локализуйте тексты кнопок и числовой формат в зависимости от локали.

    Важно: не полагайтесь только на цвета для обозначения активной страницы — добавьте видимый фокус и aria-current там, где это уместно.

    Тесты и критерии приёмки

    Критерии приёмки для компонента пагинации:

    • При загрузке с 50 элементами и itemsPerPage 10 отображается 10 элементов и 5 страниц.
    • Нажатие на номер страницы устанавливает currentPage и обновляет список.
    • Prev и Next работают корректно и дизейблятся на краях.
    • Изменение itemsPerPage сбрасывает currentPage на 1 и корректно пересчитывает страницы.
    • Навигация фокусируема с клавиатуры и поддерживает Enter/Space.
    • При ошибке fetch отображается сообщение об ошибке и возможность повторить запрос.

    Минимальные тест-кейсы:

    • Unit: вычисление pages при различных длинах data и itemsPerPage.
    • Integration: поведение renderPageNumbers при изменении currentPage и limits.
    • E2E: симуляция кликов и проверка DOM-элементов на странице.

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

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

    • Обработаны ошибки fetch.
    • Добавлена поддержка клавиатуры и aria-атрибутов.
    • Написаны модульные тесты для логики пагинации.

    QA:

    • Проверена навигация Prev/Next/номера страниц.
    • Проверено поведение при изменении itemsPerPage.
    • Проверено состояние при пустых данных и ошибках сети.

    PM / Дизайнер:

    • Подтверждены тексты и локализация.
    • Подтверждён макет для разных состояний (много страниц, мало элементов).

    Ментальные модели и хинты при проектировании

    • «Разбивай по видимому»: показывай только ту часть данных, которую пользователь видит тут и сейчас.
    • «Снижение трения навигации»: если пользователь часто переключается между страницами, предзагружай соседние страницы.
    • «Консервативное государство»: при изменении количества элементов на странице всегда возвращай пользователя к валидному номеру страницы (обычно 1).

    Типичные ошибки и когда решение не сработает

    • Загрузка всей базы данных в браузер на мобильном устройстве приведёт к OOM и медленному UI.
    • Infinite scroll улучшает вовлечение, но ухудшает способность пользователей попасть к конкретному элементу и делиться ссылками.
    • Неправильная синхронизация URL приведёт к невозможности листать историю браузера или делиться текущим состоянием.

    Производительность и масштабирование

    • Используйте useMemo и избегайте лишних перерендеров списка.
    • Для больших списков используйте виртуализацию вместо создания большого числа DOM-элементов.
    • Подумайте о lazy-loading изображений в элементах списка.

    Риски и смягчения

    • Большой объём данных в клиенте: перейти на серверную или курсорную пагинацию.
    • Нестабильный API: добавить retry и friendly error UI.
    • Проблемы доступности: добавить автоматические проверки a11y и ручное тестирование с клавиатурой и скринридером.

    Короткое объявление для команды (100–200 слов)

    Добавлен компонент клиентской пагинации для списка задач. Компонент поддерживает: выбор количества элементов на странице, номера страниц, Prev/Next, keyboard navigation и aria-атрибуты. Логика использует хуки React, оптимизирована с помощью useMemo. Для больших объёмов данных рекомендуется перейти на серверную пагинацию или применить виртуализацию. Перед релизом проверьте сценарии ошибки сети и убедитесь, что стили для активного состояния соответствуют дизайну.

    Сводка и рекомендации

    • Для небольших наборов данных client-side пагинация простая и быстрая в реализации.
    • Для больших данных используйте server-side или cursor-based подход.
    • Не забывайте про доступность, URL-синхронизацию и тестирование.

    Important: Пагинация — это не только техническая реализация. Это продуктовый выбор, влияющий на навигацию, SEO и аналитику.

    Краткие выводы:

    • Реализуйте пагинацию там, где это улучшит UX и производительность.
    • Всегда оценивайте объём данных и ожидания по консистентности перед выбором клиентской или серверной стратегии.
    Поделиться: X/Twitter Facebook LinkedIn Telegram
    Автор
    Редакция

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

    Мемоизация в JavaScript и React
    Frontend

    Мемоизация в JavaScript и React

    Как сменить поисковик по умолчанию в браузере
    Браузеры

    Как сменить поисковик по умолчанию в браузере

    Как почистить PS4 — подробный русскоязычный гид
    Руководство

    Как почистить PS4 — подробный русскоязычный гид

    Установка и использование Nest Thermostat
    Умный дом

    Установка и использование Nest Thermostat

    Как отправить Word на Kindle быстро
    Руководство

    Как отправить Word на Kindle быстро

    OBD2 и Android: мониторинг и диагностика
    Авто

    OBD2 и Android: мониторинг и диагностика