Работа с Axios

Роль Axios в архитектуре TanStack Query

TanStack Query не навязывает способ получения данных и полностью делегирует транспортный слой внешним инструментам. Axios часто используется как основной HTTP-клиент благодаря предсказуемому API, встроенным интерсепторам, автоматической сериализации JSON и удобной обработке ошибок.

В связке с TanStack Query Axios выполняет роль низкоуровневого слоя, который инкапсулируется внутри queryFn и mutationFn. Сам Query при этом отвечает за кэширование, синхронизацию состояния и повторные запросы, а Axios — за непосредственное выполнение HTTP-запросов.

Базовая интеграция Axios в queryFn

Простейшая интеграция заключается в создании функции запроса, возвращающей 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

В реальных приложениях используется единый экземпляр 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 в mutationFn

Для мутаций 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 в TanStack Query

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.

Интерсепторы Axios и 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.

Повторные запросы и retry-логика

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, обеспечивая корректную отмену запросов при смене ключа или размонтировании компонента.

Пагинация с Axios

TanStack Query часто используется для серверной пагинации.

function fetchUsers(page) {
  return api
    .get('/users', {
      params: { page },
    })
    .then(res => res.data);
}
useQuery({
  queryKey: ['users', page],
  queryFn: () => fetchUsers(page),
});

Изменение страницы автоматически создает новый ключ и новый кэш.

Инфинит-запросы и Axios

Для бесконечной загрузки данных используется 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-функций улучшает масштабируемость.

// 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.

Кэширование и Axios как источник данных

TanStack Query кэширует результат выполнения queryFn, а Axios в этом контексте является лишь источником данных. Любая оптимизация на уровне Axios (например, повторное использование HTTP-соединений или interceptors) дополняет, но не заменяет кэширование TanStack Query.

Дублирование кэширования на уровне Axios обычно избыточно и может привести к конфликтам состояний, если не контролируется явно.

Итоговая модель взаимодействия

В связке TanStack Query и Axios формируется двухслойная архитектура: Axios отвечает за транспорт, TanStack Query — за состояние, синхронизацию и кэширование. Четкое разделение обязанностей позволяет масштабировать клиентскую часть приложения без усложнения бизнес-логики запросов.