Работа с устаревшим API

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

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

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

По мере роста экосистемы React и появления конкурентных решений стало очевидно, что часть API затрудняет оптимизацию и ухудшает предсказуемость поведения. Устаревший API сохранялся для обратной совместимости, но постепенно помечался как deprecated и выносился в отдельные паттерны миграции.


Основные категории устаревших возможностей

Устаревший API TanStack Query условно делится на несколько групп, каждая из которых связана с конкретной областью управления серверным состоянием.

Колбэки жизненного цикла запросов

Ранее в useQuery активно использовались:

  • onSuccess
  • onError
  • onSettled

Эти функции позволяли выполнять побочные эффекты прямо в конфигурации запроса. Пример типичного устаревшего подхода:

useQuery({
  queryKey: ['user'],
  queryFn: fetchUser,
  onSuccess: (data) => {
    setUser(data)
  },
  onError: (err) => {
    console.error(err)
  }
})

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

  • неявным повторным вызовам эффектов
  • сложностям при тестировании
  • расхождению поведения между компонентами
  • гонкам состояния

Современная модель смещает акцент в сторону реактивных эффектов через useEffect и подписки на состояние запроса.


Устаревшие параметры кеширования

Одним из ключевых изменений стало переименование и переработка параметров управления временем жизни кеша.

Ранее использовался параметр:

  • cacheTime

В новых версиях он заменён на:

  • gcTime

Причина изменения не только косметическая. Семантика стала точнее: речь идёт не о «времени кеша», а о времени до сборки мусора неиспользуемых данных.

Старое поведение:

useQuery({
  queryKey: ['posts'],
  queryFn: fetchPosts,
  cacheTime: 1000 * 60 * 5
})

Новый подход:

useQuery({
  queryKey: ['posts'],
  queryFn: fetchPosts,
  gcTime: 1000 * 60 * 5
})

Это изменение устранило путаницу между активным кешированием и временем хранения неиспользуемых данных.


Устаревшие механизмы мутаций

В ранних версиях мутации часто сопровождались встроенными колбэками:

  • onSuccess
  • onError
  • onMutate
  • onSettled

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

Типичный устаревший пример:

useMutation({
  mutationFn: updateUser,
  onSuccess: () => {
    queryClient.invalidateQueries(['user'])
  }
})

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

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

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


Совместимость устаревшего API и поведение в новых версиях

В переходных версиях библиотеки сохранение обратной совместимости было приоритетом. Это означает, что устаревшие API:

  • продолжают работать, но могут выдавать предупреждения
  • могут иметь изменённую семантику
  • не гарантируют стабильность поведения в будущем

Поведение устаревших опций можно разделить на три категории:

1. Полностью поддерживаемые, но не рекомендуемые

Функциональность работает без изменений, но помечена как deprecated. Пример — некоторые колбэки мутаций.

2. Частично изменённые

API сохраняется, но логика внутри переработана. Пример — cacheTime → gcTime.

3. Удалённые или заменённые

Некоторые возможности были удалены и заменены альтернативными механизмами. Обычно это касается:

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

Поведение queryKey в устаревших паттернах

Одним из ключевых элементов устаревших подходов было недостаточно строгое использование queryKey.

Ранее встречались практики:

useQuery(['user', userId], fetchUser)

или даже:

useQuery('user', fetchUser)

В старых версиях допускалась строковая форма ключа, что приводило к:

  • коллизиям кеша
  • невозможности точной инвалидции
  • неоднозначной сериализации ключей

Современная модель требует структурированного массива:

useQuery({
  queryKey: ['user', userId],
  queryFn: () => fetchUser(userId)
})

Устаревший строковый формат сохраняется только для совместимости и постепенно считается техническим долгом.


Устаревшие механизмы предзагрузки и синхронизации

В ранних версиях активно использовались императивные методы:

  • prefetchQuery
  • setQueryData
  • invalidateQueries

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

Типичный устаревший паттерн:

useEffect(() => {
  queryClient.prefetchQuery(['posts'], fetchPosts)
}, [])

Проблема такого подхода:

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

Современные подходы выносят такие операции в:

  • маршрутизационные слои
  • загрузочные стратегии
  • сервисные функции

Устаревшие настройки поведения refetch

Ранее конфигурация запросов включала большое количество флагов:

  • refetchOnWindowFocus
  • refetchOnReconnect
  • refetchInterval
  • refetchIntervalInBackground

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

Особенно проблемными были комбинации:

  • автоматический refetch + агрессивный polling
  • refetch при фокусе + staleTime = 0
  • фоновые обновления без контроля дедупликации

Это приводило к избыточной сетевой активности и нестабильным UI-состояниям.


Устаревшие подходы к управлению состоянием запроса

Ранее объект результата useQuery активно использовался как универсальный контейнер:

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

Но устаревшие паттерны часто приводили к избыточной зависимости UI от всего объекта целиком:

const query = useQuery(...)

и далее:

query.data
query.refetch()
query.isLoading

Современные подходы рекомендуют деструктуризацию с выбором только необходимых полей:

const { data, isFetching, error } = useQuery(...)

Это уменьшает количество ненужных ререндеров и делает зависимость компонентов более явной.


Устаревшие конфигурации глобального клиента

QueryClient в ранних версиях позволял задавать глобальные дефолты, которые затрагивали всё приложение без строгих ограничений:

const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      cacheTime: 1000 * 60 * 5
    }
  }
})

Проблема заключалась в том, что такие настройки:

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

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


Миграционные паттерны при работе с устаревшим API

Работа с устаревшими интерфейсами обычно сводится не к прямому переписыванию всего кода, а к постепенной адаптации слоёв приложения.

Типовые стратегии:

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

Промежуточный код часто содержит смешанные стили:

useQuery({
  queryKey: ['user'],
  queryFn: fetchUser,
  onSuccess: handleUser
})

и постепенно трансформируется в более декларативную модель без встроенных побочных эффектов.


Поведенческие различия между устаревшим и текущим API

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

Ключевые различия:

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

Эти изменения делают устаревший API не просто «старым синтаксисом», а иной моделью мышления о серверном состоянии, где логика и данные были тесно связаны внутри конфигураций запросов.