Как React Query сократил 500 строк в слое API приложения React Native
Реальный кейс использования React Native, демонстрирующий, как переход от ручного получения данных с помощью useEffect к использованию React Query позволил устранить повторяющийся код и улучшить работу с кэшированием и функциями работы в офлайн-режиме.
Введение
Некоторое время назад кодовая база 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 и одновременно улучшает кэширование, поведение загрузки, эффективность сети и работу в офлайн-режиме.
Самым значительным преимуществом здесь было не просто повышение производительности.
Это было снижение сложности.
Меньше кода. Меньше ошибок. Лучший опыт для пользователей приложения.
Именно это сочетание сделало миграцию оправданной.