Главная / Статьи / Как React Query сократил 500 строк в слое API приложения React Native

Как React Query сократил 500 строк в слое API приложения React Native

Реальный кейс использования React Native, демонстрирующий, как переход от ручного получения данных с помощью useEffect к использованию React Query позволил устранить повторяющийся код и улучшить работу с кэшированием и функциями работы в офлайн-режиме.

1364 слов

Введение

Некоторое время назад кодовая база React Native, которую вы можете узнать, столкнулась с знакомой проблемой.

Почти каждый экран, требовавший удаленных данных, следовал одной и той же схеме:

  • Запросы к источникам данных, размещенные внутри useEffect
  • Несколько отдельных флагов загрузки
  • Собственные блоки обработки ошибок
  • Реализованная вручную логика обновления контента при прокрутке
  • Механизмы ручной перепробовки
  • Временные решения для локального кэширования

С функциональной точки зрения ничего не было сломано. Однако поддержание единообразия в десятках экранов превратилось в настоящую нагрузку для технического обслуживания.

Переход на React Query (от TanStack) позволил команде устранить большое количество повторяющегося кода для работы с сетью, получив взамен улучшенную кэширование, более чистую систему управления загрузкой и надежнее поведение в офлайн-режиме. Библиотека включает в себя встроенное кэширование запросов, автоматическое обновление данных на фоне и логику, учитывающую особенности сети, что значительно сокращает необходимость вручную писать механизмы управления состоянием.

Далее приведено сравнение старого ручного подхода и версии с React Query на примере реальных экранов приложения React Native.

Проблемы традиционного управления API

Настройка типичного экрана выглядела примерно так:

const [data, setData] = useState([]);
const [loading, setLoading] = useState(false);
const [refreshing, setRefreshing] = useState(false);
const [error, setError] = useState(null);

useEffect(() => {
  fetchData();
}, []);
const fetchData = async () => {
  try {
    setLoading(true);
    const response = await api.getPosts();
    setData(response);
  } catch (err) {
    setError(err);
  } finally {
    setLoading(false);
  }
};

Этот же шаблон постоянно встречался во всем приложении.

Каждый экран должен был самостоятельно управлять:

  • Флагом загрузки
  • Флагом ошибки
  • Обработкой повторных попыток
  • Логика обновления при прокрутке вверх
  • Собственный подход к кэшированию
  • Ручное запускание повторного загрузчения
  • По мере роста приложения поддержание единообразия в этом повторяющемся шаблоне становилось всё сложнее.

    Появление React Query

    Та же самая страница, переписанная с использованием React Query, сводится к следующему:

    const { data, isLoading, error, refetch } = useQuery({
      queryKey: ["posts"],
      queryFn: fetchPosts,
    });
    

    Вот и вся реализация.

    React Query автоматически обрабатывает всё, что перечислено ниже:

    • Индикаторы загрузки
    • Состояния ошибок
    • Устранение дубликатов одинаковых запросов
    • Повторный загруз данных на фоне
    • Управление кэшем
    • Повторная попытка выполнения неудачных запросов
    • Реагирование на восстановление сети

    Большинство этих функций доступны сразу без дополнительных настроек, а их можно настроить с помощью таких параметров, как staleTime, gcTime, настройки повторных попыток и параметры повторного загруза.

    Сравнение №1: сетевые запросы

    До React Query

    Представьте три отдельных экрана, которым все нужны одинаковые данные профиля пользователя.

    Без какого-либо общего слоя кэширования каждый из них отправляет собственный запрос:

    Profile Screen → API Call
    Settings Screen → API Call
    Dashboard Screen → API Call
    

    Результат: три отдельных сетевых запроса за одни и те же данные.

    С React Query

    Profile Screen → API Call
    Settings Screen → Cached Data
    Dashboard Screen → Cached Data
    

    На этот раз результат: всего один сетевой запрос.

    React Query хранит результаты под ключом запроса и делится этими данными из кэша со всеми компонентами, которые их запрашивают. Любой экран, который позже запросит тот же ключ, получает значение из кэша мгновенно, в то время как фоновое обновление может незаметно поддерживать его актуальность.

    Результат в продакшене

    На экранах, которые пользователи посещают часто, это приводит к:

    • Значительно меньшему количеству дублирующихся запросов к API
    • Меньшей нагрузке на серверы бэкенда
    • Более быстрым переходам между экранами

    Сравнение №2: Эффективность кэширования

    Можно сказать, что именно в области кэширования React Query приносит наибольшую пользу.

    Когда тот же запрос снова отправляется до того, как его кэшированная копия станет устаревшей:

    useQuery({
      queryKey: ["products"],
      queryFn: getProducts,
      staleTime: 300000,
    });
    

    Интерфейс может сразу отобразить кэшированные данные, при этом React Query может фоново их обновлять. Кэшированные записи сохраняются и в конечном итоге удаляются в соответствии с настройками, которые вы задаете.

    Реальный пример

    Рассмотрим экран списка товаров в электронной торговой приложении.

    Без кэширования открытие этого экрана означает:

    Open Products
    ↓
    Network Request
    ↓
    Navigate Back
    ↓
    Open Products Again
    ↓
    Network Request
    

    При использовании React Query тот же процесс выглядит так:

    Open Products
    ↓
    Network Request
    ↓
    Navigate Back
    ↓
    Open Products Again
    ↓
    Instant Cached Data
    

    Разница заключается в том, что приложение кажется значительно быстрее пользователям.

    Сравнение №3: Статусы загрузки

    До внедрения React Query отслеживание статуса загрузки требовало управления несколькими логическими переменными:

    const [loading, setLoading] = useState(false);
    const [refreshing, setRefreshing] = useState(false);
    const [isRetrying, setIsRetrying] = useState(false);
    

    После этого один вызов хука предоставляет всё необходимое:

    const {
      isLoading,
      isFetching,
      isRefetching,
    } = useQuery(...)
    

    React Query различает первую загрузку и любые последующие фоновые запросы, что значительно упрощает понимание логики интерфейса.

    Реальная польза

    Вместо отображения полноэкранных индикаторов загрузки при каждом запросе приложение может различать:

    • Первоначальная загрузка: полноэкранный индикатор
    • Фоновое обновление: небольшой, незаметный индикатор
    • Данные уже в кэше: полная отсутствие видимых прерываний

    Это делает приложение гораздо более отзывчивым для пользователей.

    Сравнение №4: Поддержка офлайн-режима

    Поведение приложения в офлайн-режиме — один из аспектов, которые команды часто недооценивают.

    Ручная обработка этого случая обычно выглядит так:

    Check Connectivity
    Pause Requests
    Retry Later
    Handle Errors
    Refetch On Reconnect
    

    Это означает наличие специальной логики подключения, распределённой по всему кодовому базису.

    React Query предлагает встроенное управление режимами онлайн и офлайн, а также возможность повторной загрузки данных в ответ на события восстановления соединения. В React Native это можно реализовать с помощью onlineManager вместе с механизмами отслеживания состояния сети платформы.

    Например:

    onlineManager.setEventListener(...)
    

    React Query также можно настроить на работу в режиме офлайн-в первую очередь, соответственно изменяя его поведение в сети.

    Реальный пример

    Возьмем в качестве примера приложение для чтения новостей.

    За день состояние соединения пользователя может выглядеть так:

    • Утро: онлайн
    • День: офлайн
    • Вечер: снова онлайн

    На протяжении всего этого периода ранее сохраненные статьи остаются доступными для чтения, а по восстановлении соединения новый контент может автоматически синхронизироваться.

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

    1. Приложения новостей

    Что это дает:

    • Артикулы в кэше
    • Автоматическое обновление на фоне
    • Снижение общего объема трафика API
    • Возможность продолжать чтение в офлайн-режиме

    2. Приложения для электронной коммерции

    Что это дает:

    • Списки товаров в кэше
    • Заранее загрузка категорий
    • Более быстрое навигирование между разделами
    • Заметно более плавный опыт покупок

    React Query также поддерживает заранее загрузку данных еще до начала навигации, что сокращает время ожидания.

    3. Приложения с панелями управления

    Что это дает:

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

    Этот подход особенно хорошо подходит для аналитических панелей и панелей управления.

    Что мы действительно удалили

    После завершения миграции:

    Удалено

    • Собственная реализация обработки состояния загрузки
    • Ручной код повторных попыток
    • Дублирующиеся запросы к API
    • Шаблон кода для обновления контента при прокрутке
    • Собственная логика кэширования
    • Ручное управление повторным загрузкой данных

    Добавлено

    • Сама библиотека React Query
    • Ключи запросов
    • Экземпляр QueryClient

    В итоге потребовалось примерно 500 строк кода для обработки запросов к API, которые больше не были нужны.

    Когда React Query может не понадобиться

    Использование React Query может оказаться избыточным, если:

    • Ваше приложение выполняет всего несколько запросов к API
    • Данные практически не меняются
    • Кэширование действительно не требуется
    • Для вашего случая использования не важно поведение при отсутствии интернета

    Однако для большинства приложений в производстве преимущества обычно довольно быстро перевешивают начальную кривую обучения.

    Заключение

    React Query — это не просто ещё один способ получения данных.

    Это полноценное решение для управления состоянием сервера, которое устраняет повторяющийся код API и одновременно улучшает кэширование, поведение загрузки, эффективность сети и работу в офлайн-режиме.

    Самым значительным преимуществом здесь было не просто повышение производительности.

    Это было снижение сложности.

    Меньше кода. Меньше ошибок. Лучший опыт для пользователей приложения.

    Именно это сочетание сделало миграцию оправданной.