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

Кастомная пагинация в 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
Автор
Редакция

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

Улучшение MP3 в GarageBand
Аудио

Улучшение MP3 в GarageBand

Broadcast Google Assistant для всей семьи
Смарт-дом

Broadcast Google Assistant для всей семьи

Как отключить «Не беспокоить» на iPhone
Mobile

Как отключить «Не беспокоить» на iPhone

Как включить фонарик на Android
Мобильные устройства

Как включить фонарик на Android

Как делать скриншоты в Ubuntu
Ubuntu

Как делать скриншоты в Ubuntu

Попробовать Unity 8 на Ubuntu
Ubuntu

Попробовать Unity 8 на Ubuntu