TanStack Query не навязывает способ получения данных и полностью делегирует транспортный слой внешним инструментам. Axios часто используется как основной HTTP-клиент благодаря предсказуемому API, встроенным интерсепторам, автоматической сериализации JSON и удобной обработке ошибок.
В связке с TanStack Query Axios выполняет роль низкоуровневого слоя,
который инкапсулируется внутри queryFn и
mutationFn. Сам Query при этом отвечает за кэширование,
синхронизацию состояния и повторные запросы, а Axios — за
непосредственное выполнение HTTP-запросов.
Простейшая интеграция заключается в создании функции запроса,
возвращающей axios.get или результат обработки ответа.
import axios from 'axios';
import { useQuery } from '@tanstack/react-query';
function fetchUsers() {
return axios.get('/api/users').then(res => res.data);
}
export function Users() {
const { data, isLoading, error } = useQuery({
queryKey: ['users'],
queryFn: fetchUsers,
});
if (isLoading) return 'Loading...';
if (error) return 'Error';
return (
<ul>
{data.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}
Ключевой момент заключается в том, что TanStack Query ожидает промис, а Axios уже возвращает промис, поэтому интеграция происходит естественно.
В реальных приложениях используется единый экземпляр Axios для управления базовым URL, заголовками и интерсепторами.
import axios from 'axios';
export const api = axios.create({
baseURL: 'https://example.com/api',
timeout: 10000,
});
Далее все запросы в TanStack Query строятся на основе этого экземпляра:
function fetchUserById(id) {
return api.get(`/users/${id}`).then(res => res.data);
}
Централизация позволяет управлять авторизацией, логированием и обработкой ошибок в одном месте.
TanStack Query активно использует queryKey, поэтому
параметры запроса должны синхронизироваться с функцией запроса.
import { useQuery } from '@tanstack/react-query';
import { api } from './api';
function fetchUser(id) {
return api.get(`/users/${id}`).then(res => res.data);
}
export function UserProfile({ id }) {
const { data } = useQuery({
queryKey: ['user', id],
queryFn: () => fetchUser(id),
});
return <div>{data?.name}</div>;
}
Важно, что queryKey и аргументы функции должны быть
согласованы. Несогласованность приводит к некорректному кэшированию.
Для мутаций Axios используется аналогично, но через
useMutation.
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { api } from './api';
function createUser(data) {
return api.post('/users', data).then(res => res.data);
}
export function CreateUserForm() {
const queryClient = useQueryClient();
const mutation = useMutation({
mutationFn: createUser,
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['users'] });
},
});
function handleSubmit(e) {
e.preventDefault();
const formData = new FormData(e.target);
const payload = Object.fromEntries(formData.entries());
mutation.mutate(payload);
}
return (
<form onSub mit={handleSubmit}>
<input name="name" />
<button type="submit">Create</button>
</form>
);
}
После успешной мутации выполняется инвалидация кэша, что обеспечивает актуализацию списка пользователей.
Axios по умолчанию отклоняет промис при HTTP-статусах вне диапазона 2xx. TanStack Query интерпретирует это как ошибку запроса.
function fetchUsers() {
return api.get('/users').then(res => res.data);
}
Ошибка может быть обработана через error:
const { error } = useQuery({
queryKey: ['users'],
queryFn: fetchUsers,
});
Расширенная обработка включает извлечение структуры ошибки Axios:
import axios from 'axios';
function fetchUsers() {
return api.get('/users').then(res => res.data).catch(err => {
if (axios.isAxiosError(err)) {
throw new Error(err.response?.data?.message || 'API Error');
}
throw err;
});
}
Такой подход унифицирует ошибки для TanStack Query.
Интерсепторы позволяют централизованно управлять авторизацией и реакцией на ошибки.
api.interceptors.request.use(config => {
const token = localStorage.getItem('token');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
});
Ответные интерсепторы часто используются для обработки 401:
api.interceptors.response.use(
response => response,
error => {
if (error.response?.status === 401) {
localStorage.removeItem('token');
window.location.href = '/login';
}
return Promise.reject(error);
}
);
TanStack Query в этом случае не требует дополнительных изменений, так как ошибка пробрасывается через Promise.
TanStack Query имеет встроенный механизм повторов, который взаимодействует с Axios-ошибками.
useQuery({
queryKey: ['users'],
queryFn: fetchUsers,
retry: 3,
});
Если необходимо учитывать тип ошибки Axios, retry можно сделать условным:
useQuery({
queryKey: ['users'],
queryFn: fetchUsers,
retry: (failureCount, error) => {
if (axios.isAxiosError(error)) {
const status = error.response?.status;
if (status === 404) return false;
}
return failureCount < 2;
},
});
Axios поддерживает AbortController, который полностью
совместим с TanStack Query.
function fetchUsers({ signal }) {
return api.get('/users', { signal }).then(res => res.data);
}
TanStack Query автоматически передает signal в
queryFn, обеспечивая корректную отмену запросов при смене
ключа или размонтировании компонента.
TanStack Query часто используется для серверной пагинации.
function fetchUsers(page) {
return api
.get('/users', {
params: { page },
})
.then(res => res.data);
}
useQuery({
queryKey: ['users', page],
queryFn: () => fetchUsers(page),
});
Изменение страницы автоматически создает новый ключ и новый кэш.
Для бесконечной загрузки данных используется
useInfiniteQuery.
function fetchUsers({ pageParam = 1 }) {
return api
.get('/users', {
params: { page: pageParam },
})
.then(res => res.data);
}
useInfiniteQuery({
queryKey: ['users'],
queryFn: fetchUsers,
getNextPageParam: lastPage => lastPage.nextPage,
});
Axios здесь выступает как транспорт, а логика пагинации полностью определяется TanStack Query.
При использовании TypeScript Axios удобно комбинируется с типами TanStack Query:
type User = {
id: number;
name: string;
};
function fetchUsers(): Promise<User[]> {
return api.get('/users').then(res => res.data);
}
useQuery<User[]>({
queryKey: ['users'],
queryFn: fetchUsers,
});
Типизация снижает риск рассинхронизации данных между сервером и клиентом.
Практика разделения API-функций улучшает масштабируемость.
// api/users.js
import { api } from './api';
export const usersApi = {
list: () => api.get('/users').then(res => res.data),
get: id => api.get(`/users/${id}`).then(res => res.data),
create: data => api.post('/users', data).then(res => res.data),
};
useQuery({
queryKey: ['users'],
queryFn: usersApi.list,
});
Такой подход отделяет инфраструктуру HTTP от логики TanStack Query.
TanStack Query кэширует результат выполнения queryFn, а
Axios в этом контексте является лишь источником данных. Любая
оптимизация на уровне Axios (например, повторное использование
HTTP-соединений или interceptors) дополняет, но не заменяет кэширование
TanStack Query.
Дублирование кэширования на уровне Axios обычно избыточно и может привести к конфликтам состояний, если не контролируется явно.
В связке TanStack Query и Axios формируется двухслойная архитектура: Axios отвечает за транспорт, TanStack Query — за состояние, синхронизацию и кэширование. Четкое разделение обязанностей позволяет масштабировать клиентскую часть приложения без усложнения бизнес-логики запросов.