Переход с версии 3 на 4 начинается с фундаментального изменения в экосистеме: библиотека больше не называется React Query и распространяется под именем TanStack Query.
Установка и импорт теперь строятся вокруг нового scope:
import { useQuery, useMutation, QueryClient } from '@tanstack/react-query'
Удаляется старый пакет:
npm uninstall react-query
npm install @tanstack/react-query
Дополнительно DevTools выделены в отдельный пакет:
npm install @tanstack/react-query-devtools
Ключевой момент миграции: все импорты и типы должны быть
приведены к @tanstack/react-query, иначе проект
будет собираться с конфликтами типов.
QueryClient в v4 сохраняет общую концепцию, но
становится более строгим в типизации и предсказуемым в поведении.
import { QueryClient } from '@tanstack/react-query'
const queryClient = new QueryClient({
defaultOptions: {
queries: {
retry: 2,
staleTime: 0,
},
},
})
Особое внимание требуется к defaultOptions.queries, так
как изменения здесь часто приводят к различиям в повторных запросах и
кэшировании.
Одно из ключевых изменений v4 — унификация аргумента
queryFn.
useQuery('todos', fetchTodos)
или
useQuery(['todos', id], ({ queryKey }) => fetchTodo(queryKey[1]))
useQuery({
queryKey: ['todos', id],
queryFn: ({ queryKey, signal }) => fetchTodo(queryKey[1], signal),
})
queryFn всегда получает единый контекстsignal для отмены запросов
(AbortController)queryKeyЛюбые старые вызовы вида:
useQuery('key', fn)
должны быть переписаны в объектный формат.
В v4 усилилась роль структурированных ключей.
queryKey: ['users', userId, 'profile']
Часто встречается:
useQuery('users') // ошибка в v4
Правильный вариант:
useQuery({
queryKey: ['users'],
queryFn: fetchUsers,
})
useMutation также перешёл на объектную форму.
useMutation(createTodo, {
onSuccess: () => {},
})
useMutation({
mutationFn: createTodo,
onSuccess: () => {},
})
mutationFn стал обязательным именованным полемonSuccess,
onErroruseInfiniteQuery стал более строгим в отношении
pageParam.
useInfiniteQuery({
queryKey: ['posts'],
queryFn: ({ pageParam = 0 }) => fetchPosts(pageParam),
getNextPageParam: (lastPage) => lastPage.nextCursor,
})
pageParam теперь всегда явно передаётся через
контекстpageParam стала строжеМеханика кэша в v4 не была радикально переписана, но поведение стало более предсказуемым.
queryKeyenableduseQuery({
queryKey: ['user', id],
queryFn: fetchUser,
enabled: Boolean(id),
})
В v4 enabled стал критическим инструментом
предотвращения лишних запросов и чаще используется как обязательный
guard.
placeholderData и keepPreviousData
остаются, но их поведение стало более очевидным.
useQuery({
queryKey: ['list', page],
queryFn: fetchList,
placeholderData: (prev) => prev,
})
При серверном рендеринге изменения касаются в основном строгой типизации и синхронизации кэша.
dehydrate и hydrate стали строже
типизированнымиimport { dehydrate, HydrationBoundary } from '@tanstack/react-query'
Devtools выделены в отдельный пакет и подключаются явно:
import { ReactQueryDevtools } from '@tanstack/react-query-devtools'
v4 значительно усилил типовую систему.
any по умолчаниюqueryFnПример:
useQuery<User>({
queryKey: ['user', id],
queryFn: fetchUser,
})
Часто возникает проблема:
queryFnselectuseQuery('key', fetchFn)
useQuery({
queryKey: ['key']
})
queryKey: 'users' // ошибка
queryFn: () => fetch('/api') // без AbortController
Исправление:
queryFn: ({ signal }) => fetch('/api', { signal })
Retry остался, но стал более предсказуемым в сочетании с
queryFn контекстом.
defaultOptions: {
queries: {
retry: (failureCount, error) => {
return failureCount < 2
},
},
}
@tanstack/react-queryПосле перехода на v4 часто происходит рефакторинг:
Пример фабрики ключей:
export const userKeys = {
all: ['users'],
detail: (id) => ['users', id],
}
v4 делает поведение более линейным:
Миграция с v3 на v4 фактически означает переход: