TanStack Query прошёл несколько этапов эволюции, в ходе которых его публичный API неоднократно пересматривался. Устаревшие интерфейсы не являются случайным наследием — они отражают переход библиотеки от ранней модели управления серверным состоянием к более строгой, предсказуемой архитектуре.
Ранние версии ориентировались на максимальную гибкость и минимальные ограничения. Это приводило к появлению множества опций, которые со временем становились трудно поддерживаемыми:
По мере роста экосистемы React и появления конкурентных решений стало очевидно, что часть API затрудняет оптимизацию и ухудшает предсказуемость поведения. Устаревший API сохранялся для обратной совместимости, но постепенно помечался как deprecated и выносился в отдельные паттерны миграции.
Устаревший API TanStack Query условно делится на несколько групп, каждая из которых связана с конкретной областью управления серверным состоянием.
Ранее в useQuery активно использовались:
onSuccessonErroronSettledЭти функции позволяли выполнять побочные эффекты прямо в конфигурации запроса. Пример типичного устаревшего подхода:
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
})
Это изменение устранило путаницу между активным кешированием и временем хранения неиспользуемых данных.
В ранних версиях мутации часто сопровождались встроенными колбэками:
onSuccessonErroronMutateonSettledХотя сами хуки остались, их использование в конфигурационном стиле постепенно признано менее предпочтительным в сложных приложениях.
Типичный устаревший пример:
useMutation({
mutationFn: updateUser,
onSuccess: () => {
queryClient.invalidateQueries(['user'])
}
})
Проблема заключалась в том, что мутация становилась точкой концентрации логики обновления всего состояния приложения. Это приводило к:
Современный подход предполагает явное управление через
queryClient вне конфигурации мутации или использование
отдельных слоёв логики.
В переходных версиях библиотеки сохранение обратной совместимости было приоритетом. Это означает, что устаревшие API:
Поведение устаревших опций можно разделить на три категории:
Функциональность работает без изменений, но помечена как deprecated. Пример — некоторые колбэки мутаций.
API сохраняется, но логика внутри переработана. Пример —
cacheTime → gcTime.
Некоторые возможности были удалены и заменены альтернативными механизмами. Обычно это касается:
Одним из ключевых элементов устаревших подходов было недостаточно
строгое использование queryKey.
Ранее встречались практики:
useQuery(['user', userId], fetchUser)
или даже:
useQuery('user', fetchUser)
В старых версиях допускалась строковая форма ключа, что приводило к:
Современная модель требует структурированного массива:
useQuery({
queryKey: ['user', userId],
queryFn: () => fetchUser(userId)
})
Устаревший строковый формат сохраняется только для совместимости и постепенно считается техническим долгом.
В ранних версиях активно использовались императивные методы:
prefetchQuerysetQueryDatainvalidateQueriesХотя сами методы не устарели, изменился их контекст использования. Ранее они часто применялись внутри компонентов без явной архитектурной границы.
Типичный устаревший паттерн:
useEffect(() => {
queryClient.prefetchQuery(['posts'], fetchPosts)
}, [])
Проблема такого подхода:
Современные подходы выносят такие операции в:
Ранее конфигурация запросов включала большое количество флагов:
refetchOnWindowFocusrefetchOnReconnectrefetchIntervalrefetchIntervalInBackgroundХотя они сохранились, их поведение стало более унифицированным, а часть комбинаций признана устаревшей из-за неоднозначных сценариев.
Особенно проблемными были комбинации:
Это приводило к избыточной сетевой активности и нестабильным 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
}
}
})
Проблема заключалась в том, что такие настройки:
Современные версии делают акцент на более явной и модульной конфигурации, где глобальные дефолты используются ограниченно и предсказуемо.
Работа с устаревшими интерфейсами обычно сводится не к прямому переписыванию всего кода, а к постепенной адаптации слоёв приложения.
Типовые стратегии:
cacheTime на gcTime без изменения
логикиonSuccess в внешние эффектыqueryKey к массивной структуреqueryClient операцийПромежуточный код часто содержит смешанные стили:
useQuery({
queryKey: ['user'],
queryFn: fetchUser,
onSuccess: handleUser
})
и постепенно трансформируется в более декларативную модель без встроенных побочных эффектов.
На уровне поведения изменения часто менее заметны, чем на уровне архитектуры, но именно они определяют корректность миграции.
Ключевые различия:
queryKey как единственного источника
идентичности запросаЭти изменения делают устаревший API не просто «старым синтаксисом», а иной моделью мышления о серверном состоянии, где логика и данные были тесно связаны внутри конфигураций запросов.