Кэширование данных на клиенте перестало быть опциональным. Медленные интерфейсы, избыточные сетевые запросы и неконтролируемые состояния данных съедают юзабилити и заставляют пользователей уходить. Решение? Библиотеки для управления данными. За последние годы React Query и SWR стали фаворитами в экосистеме React. Но когда выбирать одну вместо другой? Разберем на металле.
Проблема грубого кэширования
Представим типичный компонент:
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetch(`/api/users/${userId}`)
.then(res => res.json())
.then(data => setUser(data))
.finally(() => setLoading(false));
}, [userId]);
// Рендер...
}
Проблемы:
- Дублирование запросов: При монтировании двух
UserProfile
с однимuserId
— два идентичных запроса. - Бесконтрольное устаревание: При изменении данных на сервере клиент не узнает.
- Отсутствие стратегий инвалидации: Как обновлять данные после мутаций?
- Нет пагинации/префетчинга.
Ручной контроль состояния быстро превращается в лавину эффектов и рефов. Нам нужны абстракции с четкой моделью данных.
Принципы современного кэширования
Любая библиотека кэширования реализует четыре ключевых паттерна:
- Устранение дублей: Запросы с идентичным ключом выполняются однажды.
- Фоновое обновление: Устаревшие данные показываются сразу, а параллельно идет silent-запрос за свежими.
- Инвалидация: Пометить данные как устаревшие, чтобы запросить актуальные.
- Сборка мусора: Удалять неиспользуемые данные из кэша.
React Query и SWR реализуют эти паттерны, но с разным подходом.
React Query: Тяжелая артиллерия
React Query (RQ) построен на "QueryClient" — централизованном хранилище данных. Базовая выборка:
import { useQuery } from '@tanstack/react-query';
async function fetchUser(id) {
const res = await fetch(`/api/users/${id}`);
if (!res.ok) throw new Error('Network error');
return res.json();
}
function UserProfile({ userId }) {
const {
data: user,
isLoading,
isError
} = useQuery({
queryKey: ['users', userId], // Уникальный ключ
queryFn: () => fetchUser(userId),
staleTime: 5 * 60 * 1000, // 5 минут до устаревания
});
// Рендер...
}
Особенности реализации:
-
Query Keys как dependency array: Ключи (
['users', userId]
) — массив любого сериализуемого типа. RQ отслеживает их изменения для перезапроса. -
Инвалидация:
js// Где-то в обработчике мутации await updateUserName(userId, newName); queryClient.invalidateQueries(['users', userId]);
RQ автоматически повторяет запросы с таким ключом.
-
Жизненный цикл запроса:
fresh
→stale
→inactive
→ garbage collected.- Данные удаляются после 5 минут без подписчиков (настраивается).
-
Оптимистичные обновления:
RQ сохраняет снапшот состояния перед мутацией для мгновенного отката.
RQ — это полноценный менеджер состояния асинхронных данных с поддержкой дебага через DevTools.
SWR: Минимализм и скорость
SWR (Stale-While-Revalidate) фокусируется на сценариях чтения данных. Синтаксис:
import useSWR from 'swr';
const fetcher = (...args) => fetch(...args).then(res => res.json());
function UserProfile({ userId }) {
const {
data: user,
error,
isLoading
} = useSWR(`/api/users/${userId}`, fetcher, {
revalidateIfStale: true, // Автообновление устаревших данных
dedupingInterval: 2000 // Интервал дедупликации (мс)
});
// Рендер...
}
Архитектурные нюансы:
- Автоматическая дедупликация: Все хуки с одним ключом (URL) получают один экземпляр запроса.
- Focus Revalidation: При возврате на вкладку SWR автоматически обновляет данные.
- Зависимые запросы:
js
// Получаем user, затем его заказы const { data: user } = useSWR(`/api/users/${userId}`); const { data: orders } = useSWR( user ? `/api/users/${userId}/orders` : null, // Ключ-null = запрос не запускается fetcher );
- Мутация через
mutate
:jsconst { mutate } = useSWRConfig(); mutate(`/api/users/${userId}`, newUserData, { optimisticData: newUserData, // Мгновенное обновление revalidate: true // Фоновое обновление с сервера });
SWR не имеет встроенной сборки мусора, но это компенсируется малой площадью API (5-6 кб).
Сравнение: Что Когда Выбрать
RQ выигрывает если:
- Приложение взаимодействует с разными типами API (REST + GraphQL через адаптеры);
- Нужны сложные зависимости между запросами (запрос B запускается после успеха A);
- Требуются тонкие правила инвалидации (например, инвалидировать все ключи с префиксом
['posts']
); - Вам критично время жизни кэша и сборка мусора.
SWR предпочтителен если:
- Приложение работает в основном с RESTful API;
- Приоритет — скорость внедрения и минимализм кода;
- Нужны ленивые зависимые запросы через ключ-функцию;
- Бюджет на размер бандла жестко ограничен.
Производительность:
- RQ: Ресурсоемкие приложения (>100 активных подписок) требуют настройки garbage collection.
- SWR: Изящен на старте, но возможны лишние ререндеры при частом использовании
mutate
.
Продвинутые паттерны
Предзагрузка данных:
RQ:
// В event handler или useEffect
queryClient.prefetchQuery({
queryKey: ['user', userId],
queryFn: fetchUser
});
SWR:
import { mutate } from 'swr';
mutate(`/api/users/${userId}`, fetch(`/api/users/${userId}`).then(r => r.json()));
Автоматические повторные запросы при ошибках сети (retry):
Обе библиотеки настраивают политику через retry
и retryDelay
.
Бесконечная загрузка:
RQ:
const { fetchNextPage, hasNextPage } = useInfiniteQuery({
queryKey: ['posts'],
queryFn: ({ pageParam = 0 }) => fetchPosts(pageParam),
getNextPageParam: (lastPage) => lastPage.nextCursor
});
SWR:
const { data, setSize } = useSWRInfinite(
(pageIndex) => `/api/posts?page=${pageIndex}`,
fetcher
);
Заключение
- React Query предлагает всеобъемлющий контроль для крупных приложений с интенсивной работой с данными. Его сила — в предсказуемой модели жизненного цикла.
- SWR — элегантный инструмент для быстрого прототипирования и приложений с фокусом на чтении данных.
Главное — не выбор «лучшей» библиотеки, а определение требований к данным в вашем приложении. Обеим библиотекам чужда магия: вы всегда контролируете состояние и поведение. Начните с SWR для скорости и минимализма. Если проекту потребуются расширенные функции мутаций и инвалидации — React Query прикроет тыл. И всегда помните: кэширование — не замена архитектуре API, а умный буфер между пользователем и сервером.
—
Примечание: Примеры используют умолчания библиотек. Всегда сверяйтесь с официальной документацией (React Query v5, SWR 2.x), где детали API могут уточняться.