Breaking changes между версиями

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

Одним из ключевых изменений стало переименование React Query в TanStack Query и выделение пакетов под разные фреймворки.

  • React Query → @tanstack/react-query
  • Vue Query → @tanstack/vue-query
  • Solid Query → @tanstack/solid-query

Это не косметическое изменение, а отражение архитектурного сдвига: библиотека перестала быть React-центричной и стала универсальным инструментом для работы с серверным состоянием.

В более ранних версиях API было тесно связано с React, включая внутренние предположения о жизненном цикле компонентов. В новых версиях ядро полностью отделено от UI-слоя.

Переход от QueryCache к QueryClient как единому центру управления

В ранних версиях присутствовала более размытая модель, где QueryCache и MutationCache использовались как отдельные сущности, а QueryClient выступал как оркестратор.

В новых версиях усилилась роль QueryClient как центральной точки:

  • все операции инвалидации
  • обновление кеша
  • предзагрузка
  • мутации

консолидированы в одном объекте.

Изменение поведения кэша

Ранее кэширование было более «неявным»: некоторые операции могли создавать побочные эффекты в кэше без явного контроля разработчика.

Теперь поведение стало более детерминированным:

  • кэш управляется через строгие API (setQueryData, invalidateQueries)
  • обновления происходят предсказуемо
  • уменьшено количество скрытых автоматических обновлений

Ужесточение модели ключей запросов

Одним из наиболее заметных изменений стало усиление роли queryKey.

Ранее ключи могли быть строками или массивами с минимальной структурой. Новая модель требует:

  • стабильных массивов
  • детерминированной структуры
  • избегания динамических объектов внутри ключа

Пример перехода:

// устаревший подход
useQuery('users', fetchUsers)

// современный подход
useQuery({
  queryKey: ['users'],
  queryFn: fetchUsers
})

Появление объектной конфигурации стало стандартом и вытеснило позиционные аргументы.

Переход на объектную сигнатуру API

Одно из самых радикальных изменений — отказ от позиционных аргументов.

Было (старые версии)

useQuery(['users'], fetchUsers, {
  staleTime: 1000
})

Стало (новые версии)

useQuery({
  queryKey: ['users'],
  queryFn: fetchUsers,
  staleTime: 1000
})

Это изменение затронуло:

  • useQuery
  • useMutation
  • useInfiniteQuery
  • useQueryClient методы

Причины изменения:

  • расширяемость API без ломки сигнатур
  • улучшение типизации
  • единообразие конфигурации
  • уменьшение неоднозначности параметров

Изменения в поведении staleTime и cacheTime

Семантика времени жизни данных была уточнена.

staleTime

В новых версиях staleTime стал более явно определять момент устаревания данных:

  • данные считаются «свежими» строго в пределах интервала
  • после истечения времени запрос считается устаревшим, но не удаляется

cacheTime → gcTime

Произошла важная терминологическая замена:

  • cacheTime переименован в gcTime (garbage collection time)

Это изменение отражает реальную природу механизма: речь идёт не о времени кэша, а о времени до удаления неиспользуемых данных.

Изменения в системе мутаций

Мутации стали более явными и управляемыми.

Новый подход к onSuccess и invalidation

Ранее часто использовались цепочки побочных эффектов:

useMutation(addUser, {
  onSuccess: () => {
    queryClient.invalidateQueries('users')
  }
})

Теперь структура стала более декларативной:

useMutation({
  mutationFn: addUser,
  onSuccess: () => {
    queryClient.invalidateQueries({
      queryKey: ['users']
    })
  }
})

Также усилилась роль:

  • optimistic updates через setQueryData
  • rollback через onError
  • управление контекстом мутации

Отказ от автоматических refetch в некоторых сценариях

Поведение повторных запросов при фокусе окна и восстановлении сети стало более настраиваемым.

Изменения:

  • более строгий контроль refetchOnWindowFocus
  • разделение поведения по типам событий
  • возможность тонкой настройки глобальных и локальных стратегий

Ранее многие поведения были включены по умолчанию, что приводило к неожиданным запросам. В новых версиях дефолты стали более «тихими».

Изменения в Devtools

Devtools были переработаны:

  • улучшена структура отображения query cache
  • добавлена поддержка новых метаданных запросов
  • улучшена визуализация состояний stale/fresh
  • унифицировано отображение мутаций

Это отражает общий тренд: библиотека стала ориентироваться на сложные сценарии, где важно понимать состояние данных, а не только их наличие.

Улучшение типизации и переход к строгим generics

TypeScript-слой стал значительно строже.

Изменения:

  • обязательное указание queryFn в явных случаях
  • улучшенная инференция return type
  • более строгая работа с unknown в некоторых API
  • уменьшение неявных any

Пример:

useQuery({
  queryKey: ['users'],
  queryFn: fetchUsers
})

Типизация теперь тесно связана с конфигурационным объектом.

Удаление и переработка устаревших API

С каждой крупной версией происходило удаление устаревших функций:

  • отказ от некоторых алиасов setQueryData в старых формах
  • удаление legacy overloads
  • упрощение API мутаций
  • отказ от старых паттернов с callback-first подходом

Цель — уменьшение поверхности API и устранение неоднозначных сценариев.

Изменение модели подписок

Механизм подписок на изменения кэша стал более оптимизированным:

  • уменьшено количество лишних перерисовок
  • улучшена дедупликация запросов
  • переработана система наблюдателей (observers)

Теперь один query может обслуживать несколько компонентов без дублирования сетевых запросов с более строгой координацией подписок.

Сдвиг в философии: от «магии» к явному управлению состоянием

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

Ранее библиотека могла:

  • автоматически рефетчить данные
  • неявно инвалидировать запросы
  • использовать глобальные дефолты без явной декларации

В новых версиях:

  • каждое поведение конфигурируемо
  • побочные эффекты явно описываются
  • кэш становится прозрачной структурой

Это делает библиотеку ближе к предсказуемой state-machine модели, чем к «умному кэшу».

Совместимость и стратегия миграции

Основная сложность перехода между версиями заключается не в отдельных API, а в изменении концепций:

  • переход на объектную конфигурацию
  • переосмысление queryKey
  • замена cacheTime на gcTime
  • явное управление инвалидацией
  • адаптация мутаций под новый стиль

Миграция требует последовательного пересмотра архитектуры слоя данных, а не механической замены функций.