TanStack Query прошёл несколько крупных этапов эволюции, и переход между основными версиями сопровождается не просто переименованием API, а переосмыслением архитектуры управления серверным состоянием. Основные изменения касаются модели клиента, структуры конфигурации, поведения кэша, системы мутаций и типизации. Понимание этих изменений критично для миграции и поддержки приложений, использующих разные поколения библиотеки.
Одним из ключевых изменений стало переименование React Query в TanStack Query и выделение пакетов под разные фреймворки.
@tanstack/react-query@tanstack/vue-query@tanstack/solid-queryЭто не косметическое изменение, а отражение архитектурного сдвига: библиотека перестала быть React-центричной и стала универсальным инструментом для работы с серверным состоянием.
В более ранних версиях API было тесно связано с React, включая внутренние предположения о жизненном цикле компонентов. В новых версиях ядро полностью отделено от UI-слоя.
В ранних версиях присутствовала более размытая модель, где
QueryCache и MutationCache использовались как
отдельные сущности, а QueryClient выступал как
оркестратор.
В новых версиях усилилась роль QueryClient как
центральной точки:
консолидированы в одном объекте.
Ранее кэширование было более «неявным»: некоторые операции могли создавать побочные эффекты в кэше без явного контроля разработчика.
Теперь поведение стало более детерминированным:
setQueryData,
invalidateQueries)Одним из наиболее заметных изменений стало усиление роли
queryKey.
Ранее ключи могли быть строками или массивами с минимальной структурой. Новая модель требует:
Пример перехода:
// устаревший подход
useQuery('users', fetchUsers)
// современный подход
useQuery({
queryKey: ['users'],
queryFn: fetchUsers
})
Появление объектной конфигурации стало стандартом и вытеснило позиционные аргументы.
Одно из самых радикальных изменений — отказ от позиционных аргументов.
useQuery(['users'], fetchUsers, {
staleTime: 1000
})
useQuery({
queryKey: ['users'],
queryFn: fetchUsers,
staleTime: 1000
})
Это изменение затронуло:
useQueryuseMutationuseInfiniteQueryuseQueryClient методыПричины изменения:
Семантика времени жизни данных была уточнена.
В новых версиях staleTime стал более явно определять
момент устаревания данных:
Произошла важная терминологическая замена:
cacheTime переименован в gcTime (garbage
collection time)Это изменение отражает реальную природу механизма: речь идёт не о времени кэша, а о времени до удаления неиспользуемых данных.
Мутации стали более явными и управляемыми.
Ранее часто использовались цепочки побочных эффектов:
useMutation(addUser, {
onSuccess: () => {
queryClient.invalidateQueries('users')
}
})
Теперь структура стала более декларативной:
useMutation({
mutationFn: addUser,
onSuccess: () => {
queryClient.invalidateQueries({
queryKey: ['users']
})
}
})
Также усилилась роль:
setQueryDataonErrorПоведение повторных запросов при фокусе окна и восстановлении сети стало более настраиваемым.
Изменения:
refetchOnWindowFocusРанее многие поведения были включены по умолчанию, что приводило к неожиданным запросам. В новых версиях дефолты стали более «тихими».
Devtools были переработаны:
Это отражает общий тренд: библиотека стала ориентироваться на сложные сценарии, где важно понимать состояние данных, а не только их наличие.
TypeScript-слой стал значительно строже.
Изменения:
queryFn в явных случаяхunknown в некоторых APIПример:
useQuery({
queryKey: ['users'],
queryFn: fetchUsers
})
Типизация теперь тесно связана с конфигурационным объектом.
С каждой крупной версией происходило удаление устаревших функций:
setQueryData в старых
формахЦель — уменьшение поверхности API и устранение неоднозначных сценариев.
Механизм подписок на изменения кэша стал более оптимизированным:
Теперь один query может обслуживать несколько компонентов без дублирования сетевых запросов с более строгой координацией подписок.
Ключевое направление изменений между версиями — уменьшение скрытого поведения.
Ранее библиотека могла:
В новых версиях:
Это делает библиотеку ближе к предсказуемой state-machine модели, чем к «умному кэшу».
Основная сложность перехода между версиями заключается не в отдельных API, а в изменении концепций:
Миграция требует последовательного пересмотра архитектуры слоя данных, а не механической замены функций.