TanStack Query vs SWR: работа с серверными данными в React
Практика4 минРедакция Innovialabs
Коротко
Обе библиотеки кешируют серверные данные и обновляют их автоматически; SWR легче и проще для приложений, которые в основном читают данные, а TanStack Query даёт больше контроля над мутациями, инвалидацией и отладкой в крупных проектах.
Коротко
SWR — компактный хук для загрузки и обновления данных. TanStack Query — более полный набор инструментов: настройка кеша, мутации с откатом, инвалидация запросов, бесконечные списки и официальные devtools. Если приложение в основном читает данные, SWR достаточно. Если пользователи много создают, редактируют и удаляют, TanStack Query избавит от написания этой логики вручную.
Чем серверное состояние отличается от клиентского
Клиентское состояние принадлежит браузеру: открытое модальное окно, выбранная вкладка, ввод в форме. Оно ваше и всегда актуально.
Серверное состояние устроено иначе:
- оно живёт на сервере, а у вас лишь его копия;
- копия может устареть в любой момент, потому что данные меняют другие пользователи;
- загрузка асинхронна и может завершиться ошибкой;
- одни и те же данные часто нужны нескольким компонентам сразу.
Если складывать серверные данные в useState или глобальный стор, придётся вручную писать флаги загрузки, обработку ошибок, дедупликацию, повторные запросы и устаревание кеша. Обе библиотеки решают именно эту задачу.
Базовое использование
// SWR
const { data, error, isLoading } = useSWR(`/api/orders/${id}`, fetcher)
// TanStack Query
const { data, error, isPending } = useQuery({
queryKey: ['orders', id],
queryFn: () => fetchOrder(id),
})
В SWR ключом обычно служит URL. В TanStack Query ключ — массив, поэтому легко инвалидировать группу запросов, например всё, что начинается с ['orders'].
Сравнение
| Критерий | SWR | TanStack Query |
|---|---|---|
| Кеширование | Общий кеш по ключу, дедупликация параллельных запросов | Общий кеш со staleTime и временем сборки мусора для каждого запроса |
| Ревалидация | При монтировании, фокусе окна, восстановлении сети, по интервалу | Те же триггеры, настраиваются для каждого запроса |
| Мутации | mutate() и useSWRMutation | useMutation с onMutate, onError, onSettled |
| Инвалидация | По ключу или функции-фильтру | invalidateQueries по префиксу ключа |
| Пагинация | useSWRInfinite, опция keepPreviousData | useInfiniteQuery, placeholderData |
| Devtools | Официальных нет | Официальная панель |
| Размер и API | Меньше, меньше понятий | Больше, больше настроек |
Кеширование и ревалидация
SWR следует идее stale-while-revalidate: сразу показать данные из кеша, в фоне запросить свежие и подменить. По умолчанию это работает хорошо почти без настройки.
TanStack Query вводит явное понятие свежести. staleTime определяет, сколько данные считаются свежими (повторных запросов в этот период нет), а время сборки мусора — сколько неиспользуемые данные хранятся в памяти. Так можно по-разному обращаться со справочником валют и с живым статусом заказа.
Мутации
Здесь библиотеки расходятся сильнее всего.
- В SWR вы вызываете API, затем
mutate(key)для обновления или передаёте вmutateоптимистичные данные. Это просто, но связанные ключи вы координируете сами. - В TanStack Query у
useMutationесть колбэки жизненного цикла. Типичное оптимистичное обновление: сохранить прежний кеш вonMutate, записать новое значение, откатить вonErrorи вызватьinvalidateQueriesвonSettled.
const mutation = useMutation({
mutationFn: updateOrder,
onSettled: () => queryClient.invalidateQueries({ queryKey: ['orders'] }),
})
Пагинация и бесконечные списки
Обе поддерживают списки «загрузить ещё»: useSWRInfinite и useInfiniteQuery. Для обычных пронумерованных страниц обе умеют показывать предыдущую страницу, пока грузится следующая, чтобы таблица не мигала. В TanStack Query работа с курсорами и проверкой «есть ли следующая страница» описана чуть явнее.
Devtools
У TanStack Query есть официальная панель devtools: видно каждый запрос, его ключ, статус, свежесть и данные, можно вручную перезапросить или инвалидировать. В большом приложении это заметно ускоряет поиск ответа на вопрос «почему не обновилось». В SWR приходится полагаться на логи или инструменты сообщества.
Как выбрать
- Небольшое приложение, лендинг с парой запросов, дашборды «только для чтения» — SWR. Меньше кода и понятий.
- Админки, CRM, приложения с множеством форм и списков, которые должны быть синхронны — TanStack Query. Инвалидация по префиксу и структурные мутации окупаются.
- Next.js App Router с загрузкой данных в серверных компонентах — для первичных данных библиотека может не понадобиться; подключайте её там, где данные меняются на клиенте.
- Команда уже использует одну из них — сохраняйте единообразие, обе зрелые.
Частые ошибки
- Копировать полученные данные в
useStateи терять автоматические обновления. - Нестабильные ключи, например объекты, создаваемые заново при каждом рендере в SWR, из-за чего уходят лишние запросы.
- Забывать инвалидировать связанные списки после мутации.
- Ставить агрессивные интервалы обновления везде, а не только там, где данные действительно часто меняются.