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

Кастомная пагинация в React Native

6 min read Мобильная разработка Обновлено 18 Dec 2025
Кастомная пагинация в React Native
Кастомная пагинация в React Native

Код на экране ноутбука на земле

Введение

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

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

Важно: термин “страница” может означать набор элементов фиксированного размера (page-based) или позицию в потоке данных (cursor-based). Ниже — практическое руководство с примерами для React Native и FlatList.

Что такое кастомная пагинация и зачем она нужна

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

  • пользовательским интерфейсом навигации между страницами;
  • стратегией выборки данных (page-based vs cursor-based);
  • поведением при прокрутке (кнопка “Загрузить ещё” vs infinite scroll);
  • обработкой ошибок, повторных попыток и индикаторов загрузки.

Основные преимущества

  • Масштабируемость: приложение обрабатывает только нужные записи.
  • Производительность: меньше сетевых запросов одного большого объёма.
  • UX: плавная прокрутка и предсказуемая подгрузка.
  • Гибкость: легко добавить фильтры, сортировку, кеширование.

Подходы к пагинации (краткое сравнение)

  • Page-based (по страницам): API возвращает номера страниц и фиксированный pageSize. Простая реализация, подходит когда данные статичны.
  • Cursor-based (по курсору): API возвращает указатель (cursor, nextToken). Лучший выбор для изменяющихся потоков данных и при больших объёмах.
  • Offset-based: смещение + limit. Простейшая версия, но плохо масштабируется на больших таблицах.
  • Infinite scroll: подгружает при прокрутке — удобен в мобильных лентах.
  • Load More button: явный контроль со стороны пользователя, облегчает тестирование.

Мини‑методология: как выбрать стратегию

  1. Оцените объём и частоту изменений данных. Если данные часто изменяются — используйте cursor-based.
  2. Если нужен предсказуемый интерфейс и простота — page-based.
  3. Для лент и бесконечной прокрутки — infinite scroll + debounce по срабатыванию.
  4. Всегда добавляйте индикатор загрузки и обработку ошибок.

Пример ответа API (REST)

Ниже — пример простого REST-ответа, который возвращает массив книг и информацию о страницах:

{
  "data": [
    {
      "id": 1,
      "title": "The Catcher in the Rye",
      "author": "J.D. Salinger"
    },
    {
      "id": 2,
      "title": "To Kill a Mockingbird",
      "author": "Harper Lee"
    }
    // ...
  ],
  "page": 1,
  "totalPages": 5
}

Определение: page — текущая страница, totalPages — общий номер страниц. Для cursor-based API вместо page вернётся cursor/nextCursor.

Функция выборки данных (fetchBooks)

Ниже — пример простого fetch-функциона для page-based API. Этот блок кода можно вставить в утилитный файл проекта (api.js или services/books.js).

const PAGE_SIZE = 10;

const fetchBooks = async (page) => {
  try {
    const response = await fetch(`https://myapi.com/books?page=${page}&pageSize=${PAGE_SIZE}`);
    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }
    const json = await response.json();
    return {
      items: json.data || [],
      page: json.page || page,
      totalPages: json.totalPages || null
    };
  } catch (error) {
    console.error('fetchBooks error:', error);
    return { items: [], page, totalPages: null, error };
  }
};

export { PAGE_SIZE, fetchBooks };

Примечание: функция возвращает объект с items, page и totalPages — это облегчает логику управления состоянием на клиенте.

Простая реализация FlatList с подгрузкой (infinite scroll)

Ниже — упрощённый пример компонента с FlatList, который подгружает страницы при достижении конца списка. Код сохранён как рабочий пример.

import React, { useState, useEffect } from 'react';
import { FlatList, View, Text, ActivityIndicator } from 'react-native';
import { fetchBooks } from './api';

const App = () => {
  const [books, setBooks] = useState([]);
  const [currentPage, setCurrentPage] = useState(1);
  const [isLoading, setIsLoading] = useState(false);
  const [totalPages, setTotalPages] = useState(null);

  useEffect(() => {
    let mounted = true;
    const load = async () => {
      setIsLoading(true);
      const res = await fetchBooks(1);
      if (!mounted) return;
      setBooks(res.items);
      setTotalPages(res.totalPages);
      setIsLoading(false);
    };
    load();
    return () => { mounted = false; };
  }, []);

  const fetchMore = async () => {
    if (isLoading) return;
    if (totalPages && currentPage >= totalPages) return; // достигли конца

    setIsLoading(true);
    const nextPage = currentPage + 1;
    const res = await fetchBooks(nextPage);

    setCurrentPage(nextPage);
    setIsLoading(false);
    setBooks(prev => [...prev, ...res.items]);
  };

  const renderItem = ({ item }) => (
    
      {item.title}
      {item.author}
    
  );

  return (
     item.id.toString()}
      onEndReached={fetchMore}
      onEndReachedThreshold={0.1}
      ListFooterComponent={isLoading ?  : null}
    />
  );
};

export default App;

Практические замечания:

  • onEndReachedThreshold — значение 0..1, где 0.1 означает “когда осталось 10%”.
  • ListFooterComponent — полезен для отображения индикатора загрузки.
  • Добавьте дебаунс/троттлинг, если onEndReached срабатывает слишком часто.

Альтернативы и улучшения

  1. Cursor-based API: возвращайте nextCursor в ответе и запрашивайте по cursor, а не по номеру страницы. Это уменьшает проблемы, когда записи добавляются/удаляются между запросами.
  2. Кнопка “Загрузить ещё”: проще в плане UX и контроля сетевых запросов, удобна для экономии трафика.
  3. Предзагрузка следующей страницы: предзагрузка при достижении, например, 30% от конца — сокращает задержки при скролле.
  4. Кеширование: храните полученные страницы в локальном кеше (Redux, Zustand, React Query) для быстрой навигации.
  5. Оптимизация рендеринга: используйте getItemLayout, initialNumToRender, removeClippedSubviews для больших списков.

Обработка ошибок и стратегия повторов

  • Уведомляйте пользователя о сетевых ошибках (Toast, Snackbar).
  • Повторные попытки с экспоненциальным бэкоффом для временных сбоев.
  • Для критичных списков отображайте контролы “Повторить” и возможность рестарта загрузки.
  • Логируйте ошибки и метрики загрузки (Sentry, LogRocket) для анализа.

UX: когда выбирать infinite scroll vs Load More

  • Infinite scroll: лучше для бесконечных лент, когда пользователь редко переходит к определённому месту.
  • Load More button: лучше для детального контроля и когда важно предсказуемое поведение (например, каталог товаров с фильтрами).

Important: Для каталогов с фильтрами и сортировкой кнопка “Загрузить ещё” даёт пользователю возможность видеть изменения запроса и повторно запускать загрузку.

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

  • При первоначальной загрузке отображается первая страница данных.
  • При достижении конца списка срабатывает подгрузка следующей страницы.
  • При ошибке отображается сообщение об ошибке и кнопка повтора.
  • Дубликаты элементов не появляются при повторных подгрузках.
  • Когда достигнут последний элемент (totalPages или пустой ответ), подгрузка прекращается.

Ролевые чек-листы

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

  • Реализовать fetchBooks с обработкой ошибок.
  • Обеспечить индикатор загрузки (spinner) и защиту от повторных запросов (isLoading).
  • Добавить тесты для fetchMore и edge-cases.

QA:

  • Проверить поведение при медленном соединении.
  • Проверить сценарии смены фильтров и очистки списка.
  • Проверить отсутствие дубликатов и правильный порядок элементов.

Product Manager:

  • Утвердить, какой подход к пагинации нужен (page vs cursor).
  • Решить UX: infinite scroll или Load More.

Тест-кейсы и приёмочные критерии

  • TC1: Первичная загрузка — первая страница должна отображаться за разумное время.
  • TC2: Подгрузка при прокрутке — при достижении конца список увеличивается.
  • TC3: Повторный клик/быстрая прокрутка — isLoading предотвращает дублирование запросов.
  • TC4: Ошибка сервера — приложение показывает сообщение и кнопку “Повторить”.
  • TC5: Пустая страница/конец — больше запросов не выполняется.

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

  • Используйте keyExtractor с уникальным ключом (id).
  • Ограничьте количество одновременно отрисовываемых элементов (initialNumToRender).
  • Применяйте getItemLayout, если все элементы имеют одинаковую высоту — это ускорит прокрутку.
  • Удаляйте отрисованные элементы вне видимости с removeClippedSubviews.
  • Сокращайте вес сети: используйте поля select/partials в API, возвращайте только необходимые поля.

Безопасность и конфиденциальность

  • Не включайте в запросы чувствительную информацию в URL-параметрах.
  • Для приватных данных используйте авторизацию (Bearer токены) и HTTPS.
  • Для GDPR/локальных законов: минимизируйте персональные данные в ответах API и хранении на устройстве.

Пример decision flow (Mermaid)

flowchart TD
  A[Начало] --> B{Тип данных изменчив?}
  B -- Да --> C[Использовать cursor-based API]
  B -- Нет --> D[Использовать page-based API]
  C --> E{UX: infinite scroll?}
  D --> E
  E -- Да --> F[Реализовать FlatList + onEndReached + индикатор]
  E -- Нет --> G[Реализовать Load More кнопку и явную пагинацию]
  F --> H[Добавить debounce и предзагрузку]
  G --> H
  H --> I[Кеширование и обработка ошибок]
  I --> J[Готово]

Шаблон плейбука для внедрения (SOP)

  1. Выбрать стратегию (page / cursor / offset).
  2. Спроектировать API-ответ (items, page/nextCursor, hasMore/totalPages).
  3. Реализовать утилиту fetch с единообразной обработкой ошибок.
  4. На клиенте — реализовать state: items, page/cursor, isLoading, error, hasMore.
  5. Подключить FlatList с onEndReached или кнопку “Загрузить ещё”.
  6. Добавить индикаторы загрузки и повтора.
  7. Написать unit и интеграционные тесты.
  8. Провести нагрузочное тестирование на устройстве и эмуляторе.
  9. Запустить A/B тест (при необходимости) и собрать метрики UX.

Глоссарий (1‑строчные определения)

  • Пагинация: разбиение данных на порции.
  • Page-based: пагинация по номеру страницы.
  • Cursor-based: пагинация по указателю (cursor).
  • Infinite scroll: автоматическая подгрузка при прокрутке.
  • FlatList: компонент React Native для производительных списков.

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

Как избежать дублирования элементов при подгрузке?

Проверяйте уникальность id перед добавлением в массив, либо используйте Set/Map для фильтрации.

Когда лучше использовать cursor-based пагинацию?

Когда данные часто обновляются или объём очень велик — cursor уменьшает проблемы с оффсетами.

Нужно ли показывать кнопку “Загрузить ещё” на мобильных устройствах?

Зависит от UX: кнопка полезна для экономии трафика и контроля пользователя; infinite scroll — для плавной ленты.


Короткий итог: кастомная пагинация в React Native — это комбинация корректно спроектированного API, надежной fetch-логики и продуманного UI. Выберите стратегию по требованиям и масштабируйте шагами: сначала простая реализация, затем оптимизация кеша и рендеринга.

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

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

RDP: полный гид по настройке и безопасности
Инфраструктура

RDP: полный гид по настройке и безопасности

Android как клавиатура и трекпад для Windows
Гайды

Android как клавиатура и трекпад для Windows

Советы и приёмы для работы с PDF
Документы

Советы и приёмы для работы с PDF

Calibration в Lightroom Classic: как и когда использовать
Фото

Calibration в Lightroom Classic: как и когда использовать

Отключить Siri Suggestions на iPhone
iOS

Отключить Siri Suggestions на iPhone

Рисование таблиц в Microsoft Word — руководство
Office

Рисование таблиц в Microsoft Word — руководство