Axios с React

16 июня 2026
Автор

Олег Марков

Введение

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

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

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

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

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

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

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

Установка

Добавить 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

Uncontrolled Components: когда DOM управляет даннымиБезопасность в React: защита от XSS, CSRF и утечек данныхRender Props: гибкое управление рендерингом в ReactРефакторинг React-кода: техники и лучшие практикиПрофилирование React: как найти и устранить узкие местаЧастичное применение: как создавать компоненты без лишнего кодаИменование компонентов в React: соглашения и лучшие практикиЛенивая загрузка: как ускорить React-приложение в разыHOC в React: мастерство композиции компонентовuseMemo: как спасти производительность от тяжелых вычислений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 Beautiful DnD - перетаскивание элементовАнимация при монтировании компонентов в ReactМокирование APIMobX — реактивное управление состоянием в ReactМикрофронтенды с React (micro-frontends)Загрузка и индикаторыАнимация списков в ReactJotai - атомарное состояниеБесконечная прокруткаFramer Motion - библиотека анимацийEmotion — библиотека CSS-in-JSДинамические стили в ReactE2E тестирование с CypressCSSTransition - переходыCSS-in-JS — плюсы и минусыКонтекст vs Redux — когда что использоватьИспользование Chart.js в ReactAxios с ReactТестирование асинхронных компонентовОбработка ошибок API
useState в React что это и как использоватьuseTransition - плавные переходы между состояниямиuseSyncExternalStore — работа с внешними сторамиuseRef в React — создание ссылок на DOM и значенияuseOptimistic — оптимистичные обновления UIuseLayoutEffect в React — эффект до отрисовкиuseInsertionEffect — внедрение стилей до мутаций DOMuseImperativeHandle в React — настройка ref дочернего компонентаuseId — генерация уникальных идентификаторовuseFormStatus - отслеживание статуса отправки формыuseDeferredValue — отложенное обновление состоянияuseDebugValue — отладка кастомных хуковuseCallback в React — мемоизация функцийuseReducer — альтернатива useState для сложной логикиuseMemo в React: как и когда оптимизировать тяжелые вычисленияuseEffect в React что это и как использоватьuseContext — работа с контекстом в ReactuseCallback в React — мемоизация функций и оптимизация ре-рендеровuseActionState в React 19Оптимизация рендеринга в React: от теории к глубокой практикеЧто такое useRef и как его применять в ReactКак и зачем использовать React HooksУправление состоянием в React через ContextКак предотвратить лишние ре-рендеры в React: полное руководствоuseMemo vs useCallback: подробное руководство по мемоизации в ReactПравила хуков — правила использованияuseEffect vs useLayoutEffect: в чём разница и какой хук выбрать?Кастомные хуки в React — создание собственных хуковuseState продвинутое использование в 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 ₽
Подробнее

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