REST-архитектура опирается на ресурсы, стандартные HTTP-методы и предсказуемые состояния сервера. TanStack Query выступает клиентским слоем управления серверным состоянием, где ключевыми становятся кэширование, синхронизация, инвалидация и контроль актуальности данных. Корректная интеграция этих двух подходов требует согласованной модели идентификации ресурсов, стратегий обновления и правил работы с запросами.
REST строится вокруг ресурсов, доступных по стабильным URL:
GET /usersGET /users/:idPOST /usersPATCH /users/:idDELETE /users/:idВ TanStack Query каждый такой ресурс отражается в виде query key, который должен однозначно соответствовать состоянию данных.
Ключевая идея: query key — это отражение REST-ресурса и его параметров
Пример:
useQuery({
queryKey: ['users'],
queryFn: fetchUsers,
})
useQuery({
queryKey: ['user', userId],
queryFn: () => fetchUser(userId),
})
Любая вариативность запроса (фильтры, пагинация, сортировка) должна отражаться в ключе:
queryKey: ['users', { page, limit, sort }]
Отсутствие строгой структуры ключей приводит к конфликтам кэша и некорректной переиспользуемости данных.
REST предполагает использование семантики HTTP:
GET — чтениеPOST — созданиеPATCH/PUT — изменениеDELETE — удалениеTanStack Query разделяет эти операции на:
useQuery — чтениеuseMutation — изменениеСогласованность достигается за счет того, что mutation всегда должна приводить к инвалидации или обновлению query cache.
TanStack Query вводит разделение между:
Ключевые параметры:
staleTime: 1000 * 60 * 5
gcTime: 1000 * 60 * 30
REST API не предоставляет встроенного механизма клиентского кэширования, поэтому TanStack Query компенсирует это логикой:
staleTime — высокая актуальность (например,
цены, статус)staleTime — редкие изменения (например,
справочники)Серверная модель REST дополняется клиентской стратегией TTL, что снижает количество повторных запросов.
После мутаций REST-ресурсов необходимо синхронизировать кэш:
const queryClient = useQueryClient()
const mutation = useMutation({
mutationFn: createUser,
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['users'] })
},
})
Инвалидация является центральным механизмом согласования REST и клиентского состояния.
Существуют стратегии:
['users'])['user', id])setQueryData)REST-эндпоинты часто возвращают коллекции с пагинацией:
{
"data": [],
"page": 1,
"totalPages": 10
}
TanStack Query требует включения параметров в ключ:
queryKey: ['users', page]
Для cursor-based pagination используется
useInfiniteQuery:
useInfiniteQuery({
queryKey: ['users'],
queryFn: ({ pageParam }) => fetchUsers(pageParam),
initialPageParam: 0,
getNextPageParam: (lastPage) => lastPage.nextCursor,
})
REST с cursor-подходом снижает проблемы с консистентностью данных при изменении коллекции.
REST API может возвращать:
400 — ошибка запроса401 — отсутствие авторизации404 — ресурс не найден500 — серверная ошибкаTanStack Query позволяет задавать стратегию retry:
useQuery({
queryKey: ['users'],
queryFn: fetchUsers,
retry: (failureCount, error) => {
if (error.status === 404) return false
return failureCount < 3
},
})
Антипаттерн: автоматический retry для всех ошибок без фильтрации HTTP-статусов.
REST предполагает факт завершенности операции только после ответа сервера, однако UX часто требует мгновенного обновления интерфейса.
TanStack Query поддерживает optimistic updates:
useMutation({
mutationFn: updateUser,
onMutate: async (newUser) => {
await queryClient.cancelQueries(['user', newUser.id])
const previous = queryClient.getQueryData(['user', newUser.id])
queryClient.setQueryData(['user', newUser.id], newUser)
return { previous }
},
onError: (err, newUser, context) => {
queryClient.setQueryData(
['user', newUser.id],
context.previous
)
},
onSettled: (data, error, newUser) => {
queryClient.invalidateQueries(['user', newUser.id])
},
})
Ключевой принцип: REST остается источником истины, клиент — временная проекция состояния.
REST-запросы могут устаревать до завершения выполнения. TanStack
Query использует AbortController:
queryFn: async ({ signal }) => {
const res = await fetch('/api/users', { signal })
return res.json()
}
Это особенно важно при:
Отмена предотвращает race conditions и лишнюю нагрузку на API.
Корректная архитектура отделяет HTTP-логику от TanStack Query:
// api/users.js
export const fetchUsers = async () => {
const res = await fetch('/api/users')
if (!res.ok) throw new Error('Error')
return res.json()
}
// hooks/useUsers.js
export const useUsers = () =>
useQuery({
queryKey: ['users'],
queryFn: fetchUsers,
})
Такое разделение обеспечивает:
REST API часто эволюционирует:
/api/v1/users/api/v2/usersTanStack Query требует учитывать версию в ключе:
queryKey: ['v2', 'users']
Иначе возможна коллизия данных между версиями API.
REST поддерживает механизмы оптимизации:
ETagIf-None-MatchCache-ControlПри интеграции с TanStack Query возможно снижение нагрузки:
const res = await fetch('/api/users', {
headers: {
'If-None-Match': etag,
},
})
Сервер может вернуть 304 Not Modified, позволяя
сохранить кэш без изменений.
Структура hooks обычно строится по ресурсам:
useUsersuseUseruseCreateUseruseUpdateUseruseDeleteUserКаждая мутация должна явно описывать:
Неполная инвалидация приводит к десинхронизации состояния между REST и клиентом.
Такие подходы приводят к неконсистентному кэшу и дублированию запросов, что противоречит как REST-архитектуре, так и модели TanStack Query.