Axios с React

06 ноября 2026
Автор

Олег Марков

Введение

Когда вы начинаете строить React-приложения, работающие с внешними данными, перед вами встаёт вопрос: чем делать HTTP-запросы? Браузер предоставляет встроенный fetch, но на практике разработчики всё чаще выбирают Axios — и на то есть весомые причины.

Axios — это JavaScript-библиотека для выполнения HTTP-запросов как в браузере, так и в Node.js. Она построена поверх XMLHttpRequest в браузере и http/https в Node.js, предоставляя удобный, promise-based API.

Почему Axios, а не fetch?

Вот ключевые преимущества Axios перед нативным fetch:

ВозможностьfetchAxios
Автоматический JSON-парсингНет, нужен .json()Да, автоматически
Обработка ошибок HTTPНет, нужно проверять response.okДа, 4xx/5xx — это ошибки
Перехватчики запросов/ответовНетДа, interceptors
Отмена запросовAbortControllerAbortController + CancelToken
Прогресс загрузкиНетДа, onUploadProgress
Автоматические заголовкиНетДа, для JSON
Поддержка Node.jsНетДа
ТаймаутыТолько через AbortControllerВстроены

fetch возвращает успешный промис даже при статусах 404 или 500 — вам нужно вручную проверять response.ok. Axios же автоматически выбрасывает ошибку при любом HTTP-статусе вне диапазона 2xx. Это существенно упрощает обработку ошибок.

В этой статье вы узнаете, как установить Axios, сделать базовые запросы, интегрировать его в React-компоненты, настроить перехватчики, отменять запросы и работать с TypeScript-типизацией.

Курс по теме

Пройти курс React и Redux Toolkit

40 000+ студентов · рейтинг 4.8 · гарантия возврата 30 дней

Установка

Добавить Axios в проект очень просто:

npm install axios

Или с использованием yarn/pnpm:

yarn add axios
# или
pnpm add axios

Axios поставляется с встроенными TypeScript-типами, поэтому дополнительно устанавливать @types/axios не нужно.

После установки вы можете импортировать axios в любой файл:

import axios from 'axios';

Базовые методы HTTP

Axios предоставляет методы для всех основных HTTP-операций. Давайте разберём каждый из них.

GET-запрос

GET используется для получения данных с сервера. Это самый распространённый тип запроса:

import axios from 'axios';

// Простой GET-запрос
async function fetchUsers() {
  const response = await axios.get('https://jsonplaceholder.typicode.com/users');
  console.log(response.data); // Массив пользователей
}

// GET с параметрами запроса
async function fetchUserPosts(userId: number) {
  const response = await axios.get('https://jsonplaceholder.typicode.com/posts', {
    params: {
      userId: userId,
      _limit: 10,
    },
  });
  // URL станет: /posts?userId=1&_limit=10
  return response.data;
}

Обратите внимание: response.data уже содержит распарсенный JSON — Axios делает это автоматически.

POST-запрос

POST используется для создания новых ресурсов:

interface CreatePostData {
  title: string;
  body: string;
  userId: number;
}

async function createPost(data: CreatePostData) {
  const response = await axios.post(
    'https://jsonplaceholder.typicode.com/posts',
    data // Axios автоматически сериализует объект в JSON
  );
  console.log('Создан пост:', response.data);
  console.log('Статус:', response.status); // 201
  return response.data;
}

// Вызов
createPost({
  title: 'Моя первая статья',
  body: 'Содержание статьи',
  userId: 1,
});

PUT-запрос

PUT заменяет ресурс целиком:

interface Post {
  id: number;
  title: string;
  body: string;
  userId: number;
}

async function updatePost(id: number, data: Omit<Post, 'id'>) {
  const response = await axios.put<Post>(
    `https://jsonplaceholder.typicode.com/posts/${id}`,
    data
  );
  return response.data;
}

PATCH-запрос

PATCH обновляет только указанные поля ресурса:

async function patchPost(id: number, partialData: Partial<Post>) {
  const response = await axios.patch<Post>(
    `https://jsonplaceholder.typicode.com/posts/${id}`,
    partialData
  );
  return response.data;
}

// Обновляем только заголовок
patchPost(1, { title: 'Новый заголовок' });

DELETE-запрос

DELETE удаляет ресурс:

async function deletePost(id: number) {
  const response = await axios.delete(
    `https://jsonplaceholder.typicode.com/posts/${id}`
  );
  console.log('Статус удаления:', response.status); // 200
}

Использование Axios в React-компонентах

Теперь разберём, как интегрировать Axios в реальные React-компоненты с хуками useState и useEffect.

Базовый компонент с загрузкой данных

import React, { useState, useEffect } from 'react';
import axios from 'axios';

interface User {
  id: number;
  name: string;
  email: string;
  phone: string;
}

const UsersList: React.FC = () => {
  const [users, setUsers] = useState<User[]>([]);
  const [loading, setLoading] = useState<boolean>(true);
  const [error, setError] = useState<string | null>(null);

  useEffect(() => {
    const fetchUsers = async () => {
      try {
        setLoading(true);
        setError(null);
        const response = await axios.get<User[]>(
          'https://jsonplaceholder.typicode.com/users'
        );
        setUsers(response.data);
      } catch (err) {
        if (axios.isAxiosError(err)) {
          // Это ошибка Axios — у неё есть структурированные данные
          setError(err.response?.data?.message ?? err.message);
        } else {
          setError('Произошла непредвиденная ошибка');
        }
      } finally {
        setLoading(false);
      }
    };

    fetchUsers();
  }, []); // Пустой массив — запрос выполняется один раз при монтировании

  if (loading) return <div>Загрузка пользователей...</div>;
  if (error) return <div>Ошибка: {error}</div>;

  return (
    <ul>
      {users.map((user) => (
        <li key={user.id}>
          <strong>{user.name}</strong> — {user.email}
        </li>
      ))}
    </ul>
  );
};

export default UsersList;

Компонент с POST-запросом (форма)

import React, { useState } from 'react';
import axios from 'axios';

interface PostFormData {
  title: string;
  body: string;
}

interface CreatedPost extends PostFormData {
  id: number;
  userId: number;
}

const CreatePostForm: React.FC = () => {
  const [formData, setFormData] = useState<PostFormData>({
    title: '',
    body: '',
  });
  const [submitting, setSubmitting] = useState(false);
  const [result, setResult] = useState<CreatedPost | null>(null);
  const [error, setError] = useState<string | null>(null);

  const handleChange = (
    e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>
  ) => {
    const { name, value } = e.target;
    setFormData((prev) => ({ ...prev, [name]: value }));
  };

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();
    setSubmitting(true);
    setError(null);

    try {
      const response = await axios.post<CreatedPost>(
        'https://jsonplaceholder.typicode.com/posts',
        { ...formData, userId: 1 }
      );
      setResult(response.data);
      setFormData({ title: '', body: '' }); // Очищаем форму
    } catch (err) {
      if (axios.isAxiosError(err)) {
        setError(`Ошибка сервера: ${err.response?.status} — ${err.message}`);
      } else {
        setError('Неизвестная ошибка');
      }
    } finally {
      setSubmitting(false);
    }
  };

  return (
    <div>
      <form onSubmit={handleSubmit}>
        <div>
          <label htmlFor="title">Заголовок:</label>
          <input
            id="title"
            name="title"
            value={formData.title}
            onChange={handleChange}
            required
          />
        </div>
        <div>
          <label htmlFor="body">Содержание:</label>
          <textarea
            id="body"
            name="body"
            value={formData.body}
            onChange={handleChange}
            required
          />
        </div>
        <button type="submit" disabled={submitting}>
          {submitting ? 'Отправка...' : 'Создать пост'}
        </button>
      </form>

      {error && <p style={{ color: 'red' }}>{error}</p>}
      {result && (
        <div>
          <h3>Пост создан!</h3>
          <p>ID: {result.id}</p>
          <p>Заголовок: {result.title}</p>
        </div>
      )}
    </div>
  );
};

export default CreatePostForm;

Кастомный хук для запросов

Вынесем логику запросов в переиспользуемый хук:

import { useState, useEffect, useCallback } from 'react';
import axios, { AxiosRequestConfig, AxiosError } from 'axios';

interface UseFetchState<T> {
  data: T | null;
  loading: boolean;
  error: string | null;
  refetch: () => void;
}

function useFetch<T>(
  url: string,
  config?: AxiosRequestConfig
): UseFetchState<T> {
  const [data, setData] = useState<T | null>(null);
  const [loading, setLoading] = useState<boolean>(true);
  const [error, setError] = useState<string | null>(null);
  const [trigger, setTrigger] = useState(0);

  const refetch = useCallback(() => {
    setTrigger((prev) => prev + 1);
  }, []);

  useEffect(() => {
    const controller = new AbortController();

    const fetchData = async () => {
      try {
        setLoading(true);
        setError(null);
        const response = await axios.get<T>(url, {
          ...config,
          signal: controller.signal,
        });
        setData(response.data);
      } catch (err) {
        if (axios.isCancel(err)) {
          // Запрос отменён — это нормально при размонтировании компонента
          return;
        }
        const axiosError = err as AxiosError;
        setError(axiosError.message || 'Ошибка запроса');
      } finally {
        setLoading(false);
      }
    };

    fetchData();

    return () => {
      controller.abort(); // Отменяем запрос при размонтировании
    };
  }, [url, trigger]);

  return { data, loading, error, refetch };
}

// Использование
const PostsList: React.FC = () => {
  const { data: posts, loading, error, refetch } = useFetch<Post[]>(
    'https://jsonplaceholder.typicode.com/posts?_limit=5'
  );

  if (loading) return <div>Загрузка...</div>;
  if (error) return <div>Ошибка: {error} <button onClick={refetch}>Повторить</button></div>;

  return (
    <div>
      <button onClick={refetch}>Обновить</button>
      {posts?.map((post) => (
        <article key={post.id}>
          <h2>{post.title}</h2>
          <p>{post.body}</p>
        </article>
      ))}
    </div>
  );
};

Создание Axios Instance

В реальных проектах вы обычно взаимодействуете с одним API, у которого есть базовый URL, общие заголовки и настройки. Для этого Axios предоставляет возможность создания экземпляров:

// src/api/axiosInstance.ts
import axios from 'axios';

const apiClient = axios.create({
  baseURL: 'https://api.myapp.com/v1',
  timeout: 10000, // 10 секунд
  headers: {
    'Content-Type': 'application/json',
    Accept: 'application/json',
  },
});

export default apiClient;

Теперь вместо указания полного URL в каждом запросе:

// Без instance — повторяем baseURL каждый раз
await axios.get('https://api.myapp.com/v1/users');
await axios.post('https://api.myapp.com/v1/posts', data);

// С instance — чисто и без повторений
import apiClient from './api/axiosInstance';

await apiClient.get('/users');
await apiClient.post('/posts', data);

Авторизованный клиент

Часто нужно автоматически добавлять токен авторизации ко всем запросам:

// src/api/authClient.ts
import axios from 'axios';

const authClient = axios.create({
  baseURL: process.env.REACT_APP_API_URL,
  timeout: 15000,
});

// Добавляем токен перед каждым запросом
authClient.interceptors.request.use((config) => {
  const token = localStorage.getItem('accessToken');
  if (token) {
    config.headers.Authorization = `Bearer ${token}`;
  }
  return config;
});

export default authClient;

Перехватчики (Interceptors)

Interceptors — одна из самых мощных возможностей Axios. Они позволяют перехватывать запросы или ответы до их обработки в then или catch.

Request Interceptor

import axios from 'axios';

// Добавляем перехватчик запросов
const requestInterceptor = axios.interceptors.request.use(
  (config) => {
    // Выполняется перед каждым запросом
    console.log(`[Request] ${config.method?.toUpperCase()} ${config.url}`);

    // Добавляем токен авторизации
    const token = localStorage.getItem('token');
    if (token) {
      config.headers.Authorization = `Bearer ${token}`;
    }

    // Добавляем временную метку
    config.headers['X-Request-Time'] = new Date().toISOString();

    return config;
  },
  (error) => {
    // Обработка ошибки конфигурации запроса
    console.error('[Request Error]', error);
    return Promise.reject(error);
  }
);

// Удаление перехватчика, когда он больше не нужен
// axios.interceptors.request.eject(requestInterceptor);

Response Interceptor

import axios from 'axios';

// Перехватчик ответов
axios.interceptors.response.use(
  (response) => {
    // Код выполняется для любого ответа со статусом 2xx
    console.log(`[Response] ${response.status} ${response.config.url}`);
    return response;
  },
  async (error) => {
    // Код выполняется для ответов за пределами диапазона 2xx
    const originalRequest = error.config;

    if (error.response?.status === 401 && !originalRequest._retry) {
      // Токен истёк — пробуем обновить
      originalRequest._retry = true;

      try {
        const refreshToken = localStorage.getItem('refreshToken');
        const response = await axios.post('/auth/refresh', { refreshToken });
        const newToken = response.data.accessToken;

        localStorage.setItem('accessToken', newToken);
        originalRequest.headers.Authorization = `Bearer ${newToken}`;

        // Повторяем оригинальный запрос с новым токеном
        return axios(originalRequest);
      } catch (refreshError) {
        // Не удалось обновить токен — выход из системы
        localStorage.removeItem('accessToken');
        localStorage.removeItem('refreshToken');
        window.location.href = '/login';
        return Promise.reject(refreshError);
      }
    }

    if (error.response?.status === 403) {
      console.error('Недостаточно прав доступа');
    }

    if (error.response?.status === 500) {
      console.error('Ошибка сервера');
    }

    return Promise.reject(error);
  }
);

Практический пример: полная настройка instance с interceptors

// src/api/client.ts
import axios, { AxiosError, InternalAxiosRequestConfig } from 'axios';

interface ApiError {
  message: string;
  code: string;
  details?: Record<string, string[]>;
}

const client = axios.create({
  baseURL: import.meta.env.VITE_API_BASE_URL || 'http://localhost:3000/api',
  timeout: 10000,
  headers: {
    'Content-Type': 'application/json',
  },
});

// Request interceptor
client.interceptors.request.use(
  (config: InternalAxiosRequestConfig) => {
    const token = localStorage.getItem('accessToken');
    if (token) {
      config.headers.set('Authorization', `Bearer ${token}`);
    }
    return config;
  },
  (error: AxiosError) => Promise.reject(error)
);

// Response interceptor
client.interceptors.response.use(
  (response) => response,
  (error: AxiosError<ApiError>) => {
    if (error.response) {
      const { status, data } = error.response;

      switch (status) {
        case 400:
          console.error('Неверный запрос:', data.details);
          break;
        case 401:
          localStorage.clear();
          window.location.href = '/login';
          break;
        case 404:
          console.error('Ресурс не найден');
          break;
        case 429:
          console.error('Слишком много запросов, попробуйте позже');
          break;
        case 500:
          console.error('Внутренняя ошибка сервера');
          break;
      }
    } else if (error.request) {
      console.error('Нет ответа от сервера. Проверьте соединение.');
    }

    return Promise.reject(error);
  }
);

export default client;

Отмена запросов

Отмена запросов важна для предотвращения утечек памяти и race conditions в React-компонентах.

Использование AbortController (рекомендуемый способ)

AbortController — это стандартный браузерный API, который Axios поддерживает начиная с версии 0.22.0:

import React, { useState, useEffect } from 'react';
import axios from 'axios';

interface SearchResult {
  id: number;
  title: string;
}

const SearchComponent: React.FC = () => {
  const [query, setQuery] = useState('');
  const [results, setResults] = useState<SearchResult[]>([]);
  const [loading, setLoading] = useState(false);

  useEffect(() => {
    if (!query.trim()) {
      setResults([]);
      return;
    }

    const controller = new AbortController();

    const search = async () => {
      setLoading(true);
      try {
        const response = await axios.get<SearchResult[]>('/api/search', {
          params: { q: query },
          signal: controller.signal, // Передаём сигнал отмены
        });
        setResults(response.data);
      } catch (err) {
        if (axios.isCancel(err)) {
          // Запрос был отменён — это ожидаемое поведение
          console.log('Запрос отменён:', err.message);
          return;
        }
        console.error('Ошибка поиска:', err);
      } finally {
        setLoading(false);
      }
    };

    // Небольшая задержка для debounce
    const timer = setTimeout(search, 300);

    return () => {
      clearTimeout(timer);
      controller.abort(); // Отменяем предыдущий запрос при каждом изменении query
    };
  }, [query]);

  return (
    <div>
      <input
        value={query}
        onChange={(e) => setQuery(e.target.value)}
        placeholder="Поиск..."
      />
      {loading && <span>Поиск...</span>}
      <ul>
        {results.map((item) => (
          <li key={item.id}>{item.title}</li>
        ))}
      </ul>
    </div>
  );
};

CancelToken (устаревший API, для справки)

До появления AbortController Axios использовал собственный механизм CancelToken. Он всё ещё работает, но AbortController предпочтительнее:

import axios, { CancelTokenSource } from 'axios';

// Создание токена отмены
const source: CancelTokenSource = axios.CancelToken.source();

// Передача токена в запрос
axios.get('/api/data', {
  cancelToken: source.token,
});

// Отмена запроса
source.cancel('Запрос отменён пользователем');

// Проверка отмены
if (axios.isCancel(error)) {
  console.log('Запрос был отменён:', error.message);
}

Правильная отмена в React с useEffect

Каждый useEffect, который выполняет асинхронные запросы, должен возвращать функцию очистки:

useEffect(() => {
  const controller = new AbortController();
  let isMounted = true; // Флаг для предотвращения обновления размонтированного компонента

  const loadData = async () => {
    try {
      const response = await axios.get('/api/data', {
        signal: controller.signal,
      });
      if (isMounted) {
        setData(response.data);
      }
    } catch (error) {
      if (!axios.isCancel(error) && isMounted) {
        setError('Ошибка загрузки');
      }
    }
  };

  loadData();

  return () => {
    isMounted = false;
    controller.abort();
  };
}, []);

TypeScript-типизация с Axios

Axios отлично работает с TypeScript. Рассмотрим основные паттерны типизации.

Типизация ответов

Axios поддерживает Generic-параметр для типизации response.data:

import axios, { AxiosResponse } from 'axios';

interface Product {
  id: number;
  name: string;
  price: number;
  category: string;
  inStock: boolean;
}

interface ApiResponse<T> {
  data: T;
  total: number;
  page: number;
  limit: number;
}

// Типизированный GET-запрос
async function getProducts(): Promise<Product[]> {
  const response: AxiosResponse<ApiResponse<Product[]>> = await axios.get(
    '/api/products'
  );
  return response.data.data; // response.data — это ApiResponse<Product[]>
}

// Сокращённая форма через дженерик
async function getProduct(id: number): Promise<Product> {
  const { data } = await axios.get<Product>(`/api/products/${id}`);
  return data; // Уже типизировано как Product
}

Типизация ошибок

import axios, { AxiosError } from 'axios';

interface ValidationError {
  field: string;
  message: string;
}

interface ApiErrorResponse {
  error: string;
  validationErrors?: ValidationError[];
}

async function createUser(userData: CreateUserDto) {
  try {
    const response = await axios.post<User>('/api/users', userData);
    return response.data;
  } catch (error) {
    if (axios.isAxiosError(error)) {
      // error теперь типизирован как AxiosError
      const axiosError = error as AxiosError<ApiErrorResponse>;

      if (axiosError.response) {
        const { status, data } = axiosError.response;

        if (status === 422 && data.validationErrors) {
          // Обрабатываем ошибки валидации
          data.validationErrors.forEach((err) => {
            console.log(`Поле "${err.field}": ${err.message}`);
          });
        }

        throw new Error(data.error || `HTTP ${status}`);
      }
    }

    throw error; // Пробрасываем неизвестные ошибки
  }
}

Типизация конфигурации и instance

import axios, { CreateAxiosDefaults, AxiosRequestConfig } from 'axios';

// Типизированная конфигурация instance
const config: CreateAxiosDefaults = {
  baseURL: 'https://api.example.com',
  timeout: 5000,
  headers: {
    'X-API-Key': process.env.REACT_APP_API_KEY,
  },
};

const api = axios.create(config);

// Типизированная обёртка для запросов
async function apiRequest<T>(
  config: AxiosRequestConfig
): Promise<T> {
  const response = await api.request<T>(config);
  return response.data;
}

// Использование
const users = await apiRequest<User[]>({
  method: 'GET',
  url: '/users',
  params: { page: 1 },
});

Создание типизированного API-сервиса

// src/services/postsService.ts
import apiClient from '../api/client';

export interface Post {
  id: number;
  userId: number;
  title: string;
  body: string;
}

export type CreatePostDto = Omit<Post, 'id'>;
export type UpdatePostDto = Partial<CreatePostDto>;

export const postsService = {
  async getAll(params?: { userId?: number; _limit?: number }): Promise<Post[]> {
    const { data } = await apiClient.get<Post[]>('/posts', { params });
    return data;
  },

  async getById(id: number): Promise<Post> {
    const { data } = await apiClient.get<Post>(`/posts/${id}`);
    return data;
  },

  async create(dto: CreatePostDto): Promise<Post> {
    const { data } = await apiClient.post<Post>('/posts', dto);
    return data;
  },

  async update(id: number, dto: UpdatePostDto): Promise<Post> {
    const { data } = await apiClient.patch<Post>(`/posts/${id}`, dto);
    return data;
  },

  async delete(id: number): Promise<void> {
    await apiClient.delete(`/posts/${id}`);
  },
};

И компонент, использующий этот сервис:

// src/components/PostsManager.tsx
import React, { useState, useEffect } from 'react';
import { postsService, Post, CreatePostDto } from '../services/postsService';

const PostsManager: React.FC = () => {
  const [posts, setPosts] = useState<Post[]>([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState<string | null>(null);

  useEffect(() => {
    const controller = new AbortController();

    postsService
      .getAll({ _limit: 10 })
      .then(setPosts)
      .catch((err) => setError(err.message))
      .finally(() => setLoading(false));

    return () => controller.abort();
  }, []);

  const handleCreate = async (dto: CreatePostDto) => {
    try {
      const newPost = await postsService.create(dto);
      setPosts((prev) => [newPost, ...prev]);
    } catch (err) {
      console.error('Ошибка создания поста:', err);
    }
  };

  const handleDelete = async (id: number) => {
    try {
      await postsService.delete(id);
      setPosts((prev) => prev.filter((p) => p.id !== id));
    } catch (err) {
      console.error('Ошибка удаления поста:', err);
    }
  };

  if (loading) return <div>Загрузка...</div>;
  if (error) return <div>Ошибка: {error}</div>;

  return (
    <div>
      <h1>Посты ({posts.length})</h1>
      {posts.map((post) => (
        <div key={post.id} style={{ border: '1px solid #ccc', margin: 8, padding: 12 }}>
          <h3>{post.title}</h3>
          <p>{post.body}</p>
          <button onClick={() => handleDelete(post.id)}>Удалить</button>
        </div>
      ))}
    </div>
  );
};

export default PostsManager;

Параллельные запросы

Иногда нужно выполнить несколько запросов одновременно. Используйте Promise.all или axios.all:

import axios from 'axios';

// С Promise.all (предпочтительный способ)
async function loadDashboardData() {
  try {
    const [usersResponse, postsResponse, commentsResponse] = await Promise.all([
      axios.get<User[]>('/api/users'),
      axios.get<Post[]>('/api/posts?_limit=5'),
      axios.get<Comment[]>('/api/comments?_limit=10'),
    ]);

    return {
      users: usersResponse.data,
      posts: postsResponse.data,
      comments: commentsResponse.data,
    };
  } catch (error) {
    // Если хотя бы один запрос упадёт — Promise.all выбросит ошибку
    throw error;
  }
}

// С Promise.allSettled — продолжаем даже при частичных ошибках
async function loadPartialData() {
  const results = await Promise.allSettled([
    axios.get<User[]>('/api/users'),
    axios.get<Post[]>('/api/posts'),
    axios.get<Comment[]>('/api/comments'),
  ]);

  return results.map((result, index) => {
    if (result.status === 'fulfilled') {
      return result.value.data;
    } else {
      console.error(`Запрос ${index} завершился ошибкой:`, result.reason);
      return null;
    }
  });
}

Загрузка файлов

Axios поддерживает отслеживание прогресса загрузки файлов:

import axios from 'axios';
import React, { useState } from 'react';

const FileUploadComponent: React.FC = () => {
  const [progress, setProgress] = useState(0);
  const [uploading, setUploading] = useState(false);

  const handleUpload = async (file: File) => {
    const formData = new FormData();
    formData.append('file', file);
    formData.append('name', file.name);

    setUploading(true);
    setProgress(0);

    try {
      const response = await axios.post('/api/upload', formData, {
        headers: {
          'Content-Type': 'multipart/form-data',
        },
        onUploadProgress: (progressEvent) => {
          if (progressEvent.total) {
            const percentCompleted = Math.round(
              (progressEvent.loaded * 100) / progressEvent.total
            );
            setProgress(percentCompleted);
          }
        },
      });

      console.log('Файл загружен:', response.data);
    } catch (error) {
      console.error('Ошибка загрузки:', error);
    } finally {
      setUploading(false);
    }
  };

  return (
    <div>
      <input
        type="file"
        onChange={(e) => {
          const file = e.target.files?.[0];
          if (file) handleUpload(file);
        }}
        disabled={uploading}
      />
      {uploading && (
        <div>
          <progress value={progress} max={100} />
          <span>{progress}%</span>
        </div>
      )}
    </div>
  );
};

export default FileUploadComponent;

Настройка глобальных умолчаний

Вы можете настроить умолчания для всех запросов через глобальный объект конфигурации:

import axios from 'axios';

// Глобальные настройки
axios.defaults.baseURL = 'https://api.example.com';
axios.defaults.timeout = 10000;
axios.defaults.headers.common['Authorization'] = `Bearer ${getToken()}`;
axios.defaults.headers.post['Content-Type'] = 'application/json';

// Или через instance — предпочтительный способ в больших приложениях
const api = axios.create({
  baseURL: 'https://api.example.com',
  timeout: 10000,
});

Настройка через instance предпочтительнее, потому что она не загрязняет глобальный объект axios и позволяет иметь несколько независимых клиентов (например, для разных API).

Обработка ошибок — лучшие практики

Axios предоставляет удобный способ проверить, является ли ошибка AxiosError:

import axios, { AxiosError } from 'axios';

function handleAxiosError(error: unknown): string {
  if (axios.isAxiosError(error)) {
    const axiosError = error as AxiosError<{ message: string }>;

    if (axiosError.response) {
      // Сервер ответил с кодом ошибки
      const status = axiosError.response.status;
      const serverMessage = axiosError.response.data?.message;

      return serverMessage || `Ошибка сервера: ${status}`;
    } else if (axiosError.request) {
      // Запрос был сделан, но ответа нет
      return 'Нет ответа от сервера. Проверьте подключение к интернету.';
    } else {
      // Ошибка при настройке запроса
      return `Ошибка запроса: ${axiosError.message}`;
    }
  }

  // Не AxiosError
  if (error instanceof Error) {
    return error.message;
  }

  return 'Произошла неизвестная ошибка';
}

// Использование в компоненте
async function fetchData() {
  try {
    const response = await axios.get('/api/data');
    return response.data;
  } catch (error) {
    const errorMessage = handleAxiosError(error);
    // Показываем пользователю понятное сообщение
    toast.error(errorMessage);
    throw error;
  }
}

Итоги

В этой статье мы разобрали все ключевые аспекты работы с Axios в React-приложениях:

  • Установка — npm install axios, TypeScript-типы включены
  • Базовые методы — GET, POST, PUT, PATCH, DELETE с автоматическим JSON-парсингом
  • React-интеграция — использование с useState и useEffect, кастомные хуки
  • Axios Instance — создание переиспользуемого клиента с baseURL и общими заголовками
  • Interceptors — перехват запросов и ответов для авторизации и глобальной обработки ошибок
  • Отмена запросов — использование AbortController для предотвращения утечек памяти
  • TypeScript — типизация ответов, ошибок и API-сервисов
  • Параллельные запросы — Promise.all и Promise.allSettled
  • Загрузка файлов — с отслеживанием прогресса через onUploadProgress

Axios остаётся одной из самых популярных библиотек для HTTP-запросов в экосистеме React. Его богатые возможности — interceptors, автоматический JSON-парсинг, встроенная обработка ошибок и TypeScript-поддержка — делают работу с API значительно удобнее, чем с нативным fetch.

Для более сложных сценариев управления серверными данными (кэширование, фоновая синхронизация, оптимистичные обновления) рассмотрите использование Axios совместно с TanStack Query или SWR.

Стрелочка влевоИспользование Chart.js в ReactТестирование асинхронных компонентовСтрелочка вправо

Постройте личный план изучения React до уровня Middle — бесплатно!

React — часть карты развития Frontend

  • step100+ шагов развития
  • lessons30 бесплатных лекций
  • lessons300 бонусных рублей на счет

Бесплатные лекции

Все гайды по React

React тестирование с Vitest
Zustand: продвинутые паттерны, persist middleware и slicesUncontrolled Components: когда DOM управляет даннымиБезопасность в React: защита от XSS, CSRF и утечек данныхRender Props: гибкое управление рендерингом в ReactРефакторинг React-кода: техники и лучшие практикиReact с WebSocket — реалтайм в компонентахReact Native New Architecture: Fabric и JSIReact Native приложение с нуля до публикации в сторахПрофилирование React: как найти и устранить узкие местаЧастичное применение: как создавать компоненты без лишнего кодаИменование компонентов в React: соглашения и лучшие практикиЛенивая загрузка: как ускорить React-приложение в разыОшибки гидратации в React и способы их устраненияHOC в React: мастерство композиции компонентовАрхитектура больших React приложений: Feature-Sliced DesignuseMemo: как спасти производительность от тяжелых вычисленийError Boundaries: создаем надежные React-приложенияКонтролируемые компоненты в React: полный контроль над формамиCompound Components в React: создаем гибкие компоненты с мощным APIДокументирование компонентов в React: Storybook, JSDoc и READMEКомпозиция компонентов в React: строим гибкие интерфейсыКомментирование кода в React: когда и как писать комментарииCode Splitting в React: как уменьшить бандл и ускорить загрузку приложенияАсинхронные компоненты в React: новый стандарт работы с даннымиДоступность (a11y) в React: ARIA, семантика и клавиатурная навигация
Zustand — управление состоянием в ReactZod - валидация с TypeScriptYup - валидация схемXState - конечные автоматыТемизация в ReactТестирование хуковTailwind CSS с ReactSWR - библиотека для запросовStyled Components — стилизация через JSStorybook - документация компонентовSnapshots тестированиеRTK Query - работа с APIRedux Toolkit - современный ReduxRecoil — библиотека управления состоянием от FacebookВиртуализация списков с react-window: как отображать тысячи элементов без лаговReact Toastify - уведомления в ReactReact Testing LibraryСоздание таблиц в React гайд по react-tableReact Spring - анимацииРабота с формами и селектами в ReactReact Query (TanStack Query) - работа с серверомПлагины в React что это и как их использоватьReact PDF - работа с PDF файламиОбзор популярных библиотек для ReactReact Icons - библиотека иконок для ReactReact Hook Form — валидация форм в ReactReact Dropzone — загрузка файловПодключение Bootstrap к React-приложениюАнимация при монтировании компонентов в ReactМокирование APIMobX — реактивное управление состоянием в ReactМикрофронтенды с React (micro-frontends)React Beautiful DnD - перетаскивание элементовЗагрузка и индикаторыАнимация списков в ReactБесконечная прокруткаJotai - атомарное состояниеFramer Motion - библиотека анимацийEmotion — библиотека CSS-in-JSДинамические стили в ReactE2E тестирование с CypressCSSTransition - переходыCSS-in-JS — плюсы и минусыКонтекст vs Redux — когда что использоватьИспользование Chart.js в ReactAxios с ReactТестирование асинхронных компонентовОбработка ошибок API
Valtio: атомарный state management в ReactuseState в React что это и как использоватьuseTransition - плавные переходы между состояниямиuseSyncExternalStore — работа с внешними сторамиuseRef в React — создание ссылок на DOM и значенияuseOptimistic — оптимистичные обновления UIuseLayoutEffect в React — эффект до отрисовкиuseInsertionEffect — внедрение стилей до мутаций DOMuseImperativeHandle в React — настройка ref дочернего компонентаuseId — генерация уникальных идентификаторовuseFormStatus - отслеживание статуса отправки формыuseDeferredValue — отложенное обновление состоянияuseDebugValue — отладка кастомных хуковuseReducer — альтернатива useState для сложной логикиuseMemo в React: как и когда оптимизировать тяжелые вычисленияuseCallback в React — мемоизация функцийХук use() в React 19useEffect в React что это и как использоватьuseContext — работа с контекстом в ReactuseCallback в React — мемоизация функций и оптимизация ре-рендеровuseActionState в React 19React Stale Closures — устаревшие замыкания в хукахОптимизация рендеринга в React: от теории к глубокой практикеЧто такое useRef и как его применять в ReactКак и зачем использовать React HooksУправление состоянием в React через ContextНовые возможности React 19Как предотвратить лишние ре-рендеры в React: полное руководствоПравила хуков — правила использованияflushSync — синхронные обновления состояния в ReactuseEffect vs useLayoutEffect: в чём разница и какой хук выбрать?Кастомные хуки в React — создание собственных хуковuseState продвинутое использование в ReactuseMemo vs useCallback: подробное руководство по мемоизации в React
Transition API — плавные обновления интерфейса в ReactReact Suspense — приостановка рендераStrictMode в React — как находить ошибки на этапе разработкиСерверные компоненты React (RSC) — подробный разбор и практикаКак работает рендеринг в ReactЧто такое props в React и как их правильно использоватьКак работает JSX связка React и HTMLЧто такое React.js и как его использоватьКак использовать элементы в ReactКак использовать React DOM в проектеЧто такое компоненты в React и как их применятьРабота с children в ReactПорталы в React: рендер компонентов вне иерархии DOMFragment в React: группировка элементов без лишних узлов DOMCSS Modules в ReactConcurrent Mode — конкурентный режим в React
Открыть базу знаний

Лучшие курсы по теме

изображение курса

React и Redux Toolkit

Антон Ларичев
AI-тренажерыAI-тренажеры
Практика в студииПрактика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.8
3 999 ₽ 6 990 ₽
Подробнее
изображение курса

TypeScript с нуля

Антон Ларичев
AI-тренажерыAI-тренажеры
Практика в студииПрактика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.8
3 999 ₽ 6 990 ₽
Подробнее
изображение курса

Next.js - с нуля

Антон Ларичев
AI-тренажерыAI-тренажеры
Практика в студииПрактика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.7
3 999 ₽ 6 990 ₽
Подробнее

Отправить комментарий