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

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

6 min read Frontend Обновлено 08 Apr 2026
Пагинация в React — клиентская реализация с хуками
Пагинация в React — клиентская реализация с хуками

Ноутбук на столе у окна с фрагментом JavaScript-кода на экране.

Зачем нужна пагинация

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

Важно: “пагинация” — это общий приём. Он не отменяет необходимость оптимизации запросов, кеширования и ленивой загрузки.

Варианты реализации клиентской пагинации

Клиентская пагинация означает, что все данные загружены в браузер, а логика показа страниц выполняется локально. Основные подходы:

  • Постраничная пагинация (page-based). Деление на фиксированные страницы по N элементов. Подходит для каталогов, поисковых результатов.
  • Бесконечная прокрутка (infinite scroll). Данные подгружаются по мере прокрутки. Подходит для фидов и контента, где пользователь скроллит последовательно.

Когда выбрать клиентскую пагинацию:

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

Когда она не подходит:

  • Данные огромные (десятки тысяч/миллионы записей).
  • Нужны обновления на сервере в реальном времени, и важно показывать только актуальные страницы.

Открытый блокнот с чистыми страницами.

Быстрый пример: постраничная пагинация в React с хуками

Ниже показан практический пример. Предположим, проект создан через Vite или create-react-app. Компонент размещаем в src/components/pagination.

1) Стартовый компонент и состояния

Создайте React-функциональный компонент и определите состояния:

import React, { useEffect, 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, setpageNumberLimit] = useState(5);  
  const [maxPageNumberLimit, setmaxPageNumberLimit] = useState(5);  
  const [minPageNumberLimit, setminPageNumberLimit] = useState(0);  
  return (  
    <>  
    

Pagination Component


          ); } export default Pagination;

Объяснение переменных в одной строке:

  • data — массив всех записей, загруженных в компонент.
  • currentPage — текущая страница (номер).
  • itemsPerPage — сколько элементов показываем на странице.
  • pageNumberLimit — сколько номеров страниц показываем в навигации одновременно.
  • maxPageNumberLimit, minPageNumberLimit — окно видимых номеров страниц.

2) Получение данных и рендер списка

Пример получения данных из JSONPlaceholder и простая функция отображения:

 useEffect(() => {  
    fetch("https://jsonplaceholder.typicode.com/todos")  
      .then((response) => response.json())  
      .then((json) => setData(json));  
  }, []);  
  
  const displayData = (data) => {  
    return (  
      
            {data.length > 0 &&           data.map((todo, index) => {             return {todo.title};           })}       
    );   };

Затем в return показываем список:

return (  
  <>  
   

Pagination Component


    {displayData(data)}    );

Этот код выведет все загруженные элементы. Если элементов много — UX пострадает. Далее реализуем ограничение по страницам.

3) Логика расчёта страниц и выбор элементов для текущей страницы

Определяем количество страниц и элементы для отображения:

 const pages = [];  
 for (let i = 1; i <= Math.ceil(data.length / itemsPerPage); i++) {  
   pages.push(i);  
 }

Затем вычисляем индексы и извлекаем подмассив для текущей страницы:

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

И используем displayData для отображения только pageItems:

return (  
  <>  
   

Pagination Component


    {displayData(pageItems)}    );

Теперь переключение currentPage влияет на показимый поднабор.

4) Кнопки навигации и номера страниц

Добавим обработчик клика по номеру страницы и генерацию списка номеров:

  const handleClick = (event) => {  
    setcurrentPage(Number(event.target.id));  
  };  
  
  const renderPageNumbers = pages.map((number) => {  
    if (number < maxPageNumberLimit +1 && number > minPageNumberLimit) {  
      return (  
        
  •           {number}         
  •       );     } else {       return null;     }   });

    Обработчики Prev/Next:

      const handleNextbtn = () => {  
        setcurrentPage(currentPage + 1);  
        if (currentPage + 1 > maxPageNumberLimit) {  
          setmaxPageNumberLimit(maxPageNumberLimit + pageNumberLimit);  
          setminPageNumberLimit(minPageNumberLimit + pageNumberLimit);  
        }  
      };  
      
      const handlePrevbtn = () => {  
        setcurrentPage(currentPage - 1);  
        if ((currentPage - 1) % pageNumberLimit == 0) {  
          setmaxPageNumberLimit(maxPageNumberLimit - pageNumberLimit);  
          setminPageNumberLimit(minPageNumberLimit - pageNumberLimit);  
        }  
      };

    И окончательная разметка навигации:

     return (  
        <>  
          

    Pagination Component


          {displayData(pageItems)}       
                  
    •                          
    •                 {renderPageNumbers}             
    •                          
    •       
           );

    Важно: подписи на кнопках в коде сохранены как в исходнике. При локализации UI можно заменить “Previous”/“Next” на русский текст.

    Доступность и UX

    • Добавьте aria-метки и правильные роли (role=”navigation”) для кнопок и списка страниц.
    • Обеспечьте фокусируемость и управление с клавиатуры (Enter/Space для кнопок страниц).
    • Подумайте о состоянии загрузки (skeleton/loader) при переключении страниц.
    • Для мобильных — уменьшите количество одновременных номеров страниц, используйте сжатые контролы.

    Когда выбрать готовую библиотеку

    Готовые библиотеки (например, react-paginate) ускоряют разработку. Они полезны если:

    • Вам нужно быстрое, проверенное решение.
    • Требуется консистентный UI и поддержка edge case’ов.
    • Вы не хотите тратить время на тестирование и полировку поведения переключения страниц.

    Минусы библиотек:

    • Меньше гибкости для нестандартного UI.
    • Размер библиотеки может быть избыточен для простого кейса.

    Когда необходима серверная пагинация

    Серверная пагинация — логика запросов и разбиение результатов выполняется на сервере. Её стоит выбирать, если:

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

    Альтернативы и гибриды

    • Комбинированная схема: серверная пагинация + клиентский кеш для недавних страниц.
    • Виртуализированный список (react-window, react-virtualized) вместо пагинации для длинных списков, когда важна плавная прокрутка.
    • Бесконечная прокрутка с опцией “Загрузить ещё” для контроля пользователя.

    Ментальные модели и эвристики

    • “Меньше на экране — легче воспринять”: держите 10–50 элементов на страницу для списков с короткими карточками.
    • “Пользовательская цель важнее”: если пользователь ищет конкретный элемент — серверный поиск + фильтры важнее бесконечного скролла.
    • “Локальная скорость vs свежесть данных”: клиентская пагинация быстрее при навигации, но может показывать устаревшие данные.

    Критерии приёмки

    • UI показывает не более itemsPerPage элементов на странице.
    • Переключение страниц изменяет содержимое без перезагрузки страницы.
    • Кнопки Previous и Next корректно блокируются на границах.
    • Номера страниц корректно отображаются с учётом min/max окна.
    • Адаптивность: навигация остаётся удобной на мобильных.
    • Доступность: элементы управления доступны с клавиатуры и имеют aria-атрибуты.

    Руководство по внедрению (пошаговый SOP)

    1. Оцените объём данных. Если > несколько тысяч — планируйте серверную пагинацию или виртуализацию.
    2. Выберите число itemsPerPage по UX (10–20 для таблиц, 20–50 для карточек).
    3. Реализуйте базовый компонент и напишите unit-тесты для функционала переключения и расчёта индексов.
    4. Добавьте индикаторы загрузки и обработку ошибок сетевых запросов.
    5. Проведите ручное тестирование на мобильных и в старых браузерах.
    6. Прогоните accessibility audit (axe, Lighthouse).
    7. Подготовьте документацию для команды и тест-кейсы для QA.

    Чеклист ролей

    • Developer:
      • Реализовал расчёт страниц и навигацию.
      • Добавил aria-атрибуты и фокус-менеджмент.
      • Покрыл ключевые функции unit-тестами.
    • QA:
      • Проверил переключение страниц, блокировку Prev/Next, крайние случаи при 0 и 1 элементе.
      • Тестировал на мобильных и с клавиатурой.
    • Product / PM:
      • Подтвердил число элементов на странице.
      • Утвердил поведение при отсутствии данных и при ошибках сети.

    Тесты и критерии приёмки (минимум)

    • Unit: расчёт indexOfFirstItem/indexOfLastItem при разных currentPage и itemsPerPage.
    • Integration: переключение на страницу X отображает ожидаемые элементы.
    • E2E: клики Prev/Next и по номеру страницы ведут к корректному состоянию и URL (если используется роутинг).

    Примеры отказов и подводные камни

    • Попытка грузить весь каталог из базы в браузер при миллионах записей — приведёт к OOM и медленной работе.
    • Несоответствие itemsPerPage между сервером и клиентом — возможны дубли и пропуски.
    • Отсутствие обработки ошибок fetch — баги при недоступном API.

    Небольшой чек: добавление URL-синхронизации

    Для удобства и восстановления состояния добавьте синхронизацию currentPage с query-параметром URL (например, ?page=3). Это даёт:

    • Возможность отправить ссылку на конкретную страницу.
    • Сохранение состояния при перезагрузке.

    Пример (React Router): обновляйте query при изменении currentPage и читайте его при mount.

    Decision flow (выбор подхода)

    flowchart TD
      A[Нужна пагинация?] --> B{Объём данных}
      B --> |Малый / Средний| C[Клиентская пагинация с хуками]
      B --> |Большой| D[Серверная пагинация или виртуализация]
      C --> E{Требуется бесконечный UX?}
      E --> |Да| F[Infinite scroll + опционально 'Загрузить ещё']
      E --> |Нет| G[Стандартная постраничная навигация]
      D --> H[API с параметрами page & limit]

    Пример улучшенного компонента (шаблон)

    Ниже — компактный шаблон компонента (сохраните логику расчётов и aria атрибуты при адаптации в проект):

    import React, { useEffect, useState } from 'react';
    
    export default function PaginationExample() {
      const [data, setData] = useState([]);
      const [currentPage, setCurrentPage] = useState(1);
      const [itemsPerPage] = useState(10);
    
      useEffect(() => {
        let mounted = true;
        fetch('https://jsonplaceholder.typicode.com/todos')
          .then(r => r.json())
          .then(json => { if (mounted) setData(json); })
          .catch(() => { if (mounted) setData([]); });
        return () => { mounted = false; };
      }, []);
    
      const totalPages = Math.max(1, Math.ceil(data.length / itemsPerPage));
    
      const changePage = (num) => {
        setCurrentPage(Math.min(Math.max(1, num), totalPages));
      };
    
      const start = (currentPage - 1) * itemsPerPage;
      const pageItems = data.slice(start, start + itemsPerPage);
    
      return (
        
      );
    }

    Этот пример показывает более безопасную загрузку, защиту от выхода за границы страниц и базовую доступность.

    Итог и рекомендации

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

    Краткое резюме: пагинация — это не только кнопки. Это выбор архитектуры данных, UX и компромисс между скоростью и актуальностью.

    Важно: тестируйте на реальных объемах и устройствах перед финальным релизом.

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

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

    CHKDSK в Windows 10 — как проверить и исправить диск
    Windows 10

    CHKDSK в Windows 10 — как проверить и исправить диск

    Искать файлы Google Drive из адресной строки Chrome
    Советы

    Искать файлы Google Drive из адресной строки Chrome

    Credential Manager в Windows — управление паролями
    Безопасность

    Credential Manager в Windows — управление паролями

    MailTrack в Opera: узнавайте когда прочли письма
    Productivity

    MailTrack в Opera: узнавайте когда прочли письма

    Простой калькулятор на Python с Tkinter
    Python GUI

    Простой калькулятор на Python с Tkinter

    Как найти клиентов на Facebook — практическое руководство
    Маркетинг

    Как найти клиентов на Facebook — практическое руководство