логотип PurpleSchool
логотип PurpleSchool

Использование API в React-приложениях

Автор

Олег Марков

Введение

Работа с внешними API — неотъемлемая часть большинства современных React-приложений. Благодаря API вы получаете данные с серверов, публикуете новые записи, обрабатываете события пользователей. Смотрите, я расскажу вам, как правильно интегрировать запросы к API в React-проекты, чтобы получить надежное, отзывчивое и эффективное приложение.

Вы узнаете, как реализовать сетевые запросы при помощи стандартного fetch, сторонних библиотек типа axios, научитесь обрабатывать ошибки и контролировать состояние загрузки данных. Я покажу, как удобно хранить данные в состоянии компонента или приложения, расскажу о подходах к размещению логики работы с API. В статье рассмотрю продвинутые паттерны: кеширование, отмену запросов, обработку авторизации. Любую теоретическую часть я обязательно подкреплю примерами — вы увидите, как всё реализуется на практике.

Что такое API и зачем он нужен в React

Перед тем как перейти к примерам, давайте уточним основные моменты. API (Application Programming Interface) — это способ обмена данными между клиентом (вашим приложением на React) и сервером (например, сервером базы данных). Обычно используется REST API или GraphQL. Вся современная frontend-разработка построена вокруг получения, изменения и удаления данных на сервере.

С React любые сторонние данные (списки товаров, профили пользователей, содержимое постов или комментариев) почти всегда загружаются через API. Правильная интеграция этих запросов — залог стабильности и удобства приложения.

Взаимодействие с API - важная часть разработки современных React-приложений. Это позволяет получать данные с сервера и отображать их в пользовательском интерфейсе. Если вы хотите научиться использовать API в React, узнаете о различных способах отправки запросов и обработки ответов — приходите на наш большой курс Основы React, React Router и Redux Toolkit. На курсе 177 уроков и 17 упражнений, AI-тренажеры для безлимитной практики с кодом и задачами 24/7, решение задач с живым ревью наставника, еженедельные встречи с менторами.

Организация запросов к API в React

Где размещать запросы к API

В React запросы размещаются:

  • Внутри useEffect — для запросов, происходящих при монтировании компонента.
  • В обработчиках событий — когда пользователь что-то делает (например, оставляет комментарий).
  • В специализированных хуках — reusable-логика для переиспользования.
  • В context/provider — для глобального хранение и рассылки данных.

Простой пример: загрузка списка задач при открытии компонента.

import React, { useState, useEffect } from "react";

function TodoList() {
  const [todos, setTodos] = useState([]);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    // Запрос к API при первом рендере компонента
    fetch("https://jsonplaceholder.typicode.com/todos")
      .then((response) => response.json()) // Преобразуем ответ в JSON
      .then((data) => {
        setTodos(data); // Сохраняем полученные данные
        setLoading(false); // Отключаем индикатор загрузки
      });
  }, []); // Пустой массив зависимостей - вызов только при монтировании

  if (loading) return <div>Загрузка...</div>;

  return (
    <ul>
      {todos.map(todo => (
        <li key={todo.id}>{todo.title}</li> // Выводим названия задач
      ))}
    </ul>
  );
}

Обратите внимание, как я вынес запрос в useEffect и сразу обработал состояние загрузки.

Использование fetch

Метод fetch — стандартный способ отправки HTTP-запросов из браузера.

fetch("https://jsonplaceholder.typicode.com/posts", {
  method: "POST", // Указываем метод запроса
  headers: { "Content-Type": "application/json" }, // Заголовки
  body: JSON.stringify({
    title: "Post title",
    body: "Post body",
    userId: 1
  }) // Данные запроса приводим к строке
})
  .then((res) => res.json()) // Обрабатываем ответ
  .then((data) => {
    // Используем полученные данные
    console.log(data);
  });

Тем не менее, fetch не покрывает автоматическую обработку ошибок и отмену запросов. Поэтому многие выбирают альтернативы.

Использование axios

Библиотека axios — один из самых популярных инструментов для запросов к API в React.

  • По умолчанию обрабатывает JSON.
  • Удобнее настраивать заголовки, interceptor'ы, отмену запросов.
import axios from "axios";

axios
  .get("https://jsonplaceholder.typicode.com/todos/1")
  .then((response) => {
    // response.data — нужные вам данные
    console.log(response.data);
  })
  .catch((error) => {
    // Ловим ошибки
    console.error("Ошибка при загрузке данных:" , error);
  });

Теперь давайте рассмотрим, как можно интегрировать axios в React-компонент:

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

function UserData() {
  const [user, setUser] = useState(null);

  useEffect(() => {
    // асинхронная функция запроса
    async function fetchData() {
      try {
        const res = await axios.get("https://jsonplaceholder.typicode.com/users/2");
        setUser(res.data); // Сохраняем пользователя в состояние
      } catch (err) {
        console.error(err);
      }
    }
    fetchData(); // Вызываем функцию после первого рендера
  }, []); // Следим только за маунтом

  if (!user) return <span>Загрузка...</span>;

  return (
    <div>
      <h3>Имя: {user.name}</h3>
      <p>Email: {user.email}</p>
    </div>
  );
}

Я использовал try/catch, чтобы перехватывать возможные ошибки запроса.

Обработка ошибок и управление состояниями

Как обрабатывать ошибки при запросах

Работа с ошибками — критически важна для пользовательского опыта. Проще всего добавить отдельное состояние для ошибки:

const [error, setError] = useState(null);
// ...

useEffect(() => {
  fetch(...)
    .then((response) => {
      if (!response.ok) {
        // Если сервер вернул ошибку
        throw new Error("Ошибка сервера");
      }
      return response.json();
    })
    .then(data => setData(data))
    .catch(err => {
      setError(err.message); // Сохраняем текст ошибки
      setLoading(false);
    });
}, []);

Теперь отображаем ошибку в компоненте:

if (error) return <div>Ошибка: {error}</div>;

Состояние загрузки

Смотрите, я выделил отдельное состояние loading, чтобы вы могли показать индикатор загрузки. Это делается через useState:

const [loading, setLoading] = useState(true);
// После успешного получения данных:
setLoading(false);

Управление и хранение данных

Обычно полученные данные с API хранят во внутреннем состоянии компонента (useState), иногда — в глобальном состоянии (например, через Redux, Zustand, React Context, Jotai и др.).

Покажу с Context:

// создаем контекст
const DataContext = React.createContext();

function DataProvider({ children }) {
  const [data, setData] = useState([]);

  // Функция загрузки
  const fetchData = async () => {
    const res = await fetch(...);
    setData(await res.json());
  };

  useEffect(() => { fetchData(); }, []);

  return (
    <DataContext.Provider value={{ data, fetchData }}>
      {children}
    </DataContext.Provider>
  );
}
// В любом дочернем компоненте:
const { data } = React.useContext(DataContext);

Контекст удобен, если вы используете данные в разных компонентах дерева.

Как делать POST, PUT, DELETE запросы

До этого мы рассматривали только GET-запросы. Смотрите, чтобы создать, обновить или удалить данные, используйте методы запроса POST, PUT (или PATCH), DELETE.

Пример POST-запроса с fetch

fetch("https://jsonplaceholder.typicode.com/posts", {
  method: "POST",
  headers: {"Content-Type": "application/json"},
  body: JSON.stringify({ title: "foo", body: "bar", userId: 1 })
})
  .then(response => response.json())
  .then(newPost => {
    // получаем добавленный пост
    console.log(newPost);
  });

Пример PUT-запроса

fetch("https://jsonplaceholder.typicode.com/posts/1", {
  method: "PUT",
  headers: {"Content-Type": "application/json"},
  body: JSON.stringify({ title: "новый заголовок", body: "новый текст" })
})
  .then(res => res.json())
  .then(updatedPost => {
    // получаем обновленную запись
    console.log(updatedPost);
  });

Пример DELETE-запроса

fetch("https://jsonplaceholder.typicode.com/posts/1", {
  method: "DELETE"
})
  .then(res => {
    if (res.ok) {
      // запись удалена
      console.log("Запись удалена");
    }
  });

Как видите — к свойству method просто передается нужный HTTP-метод. Не забывайте о правильных заголовках.

Хуки для работы с API: useEffect, useCallback, useReducer

useEffect

Я часто использую useEffect для загрузки данных при монтировании:

useEffect(() => {
  fetch("...")
    .then(res => res.json())
    .then(setData);
}, []); // [] — вызов только при маунте

useCallback

Если вы передаете функцию для изменения данных в дочерние компоненты — оборачивайте её в useCallback, чтобы не возникало лишних перерисовок.

const addItem = useCallback(
  (item) => {
    // Код для добавления данных (например, POST)
  },
  []  // зависимости, если есть
);

useReducer

Для сложных состояний, например, нескольких вложенных сущностей (данные, ошибки, состояние загрузки) удобно использовать useReducer:

const initialState = { data: null, loading: true, error: null };

function reducer(state, action) {
  switch (action.type) {
    case "SUCCESS":
      return { ...state, data: action.data, loading: false };
    case "ERROR":
      return { ...state, error: action.error, loading: false };
    default:
      return state;
  }
}

const [state, dispatch] = useReducer(reducer, initialState);
// ...

Этот подход часто используется вместе с кастомными хуками.

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

Создание собственного хука для запросов позволяет переиспользовать логику и сделать код чище.

Пример useFetch

Вот простой кастомный хук для GET-запросов:

import { useState, useEffect } from "react";

export function useFetch(url) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    setLoading(true);
    fetch(url)
      .then((res) => {
        if (!res.ok) throw new Error("Ошибка");
        return res.json();
      })
      .then(setData)
      .catch((err) => setError(err))
      .finally(() => setLoading(false));
  }, [url]); // перезапускаем при смене url

  return { data, loading, error };
}

Теперь используйте хук так:

const { data, loading, error } = useFetch("https://jsonplaceholder.typicode.com/posts");

Можно его расширить и для POST/PUT/DELETE.

Использование сторонних библиотек для работы с API

React Query и SWR

Для большинства проектов всё чаще применяют React Query или SWR. Эти библиотеки берут на себя:

  • Кеширование данных;
  • Повторные запросы при ошибках;
  • Инвалидизацию данных;
  • Автоматическую синхронизацию с сервером.

Пример React Query

import { useQuery } from "@tanstack/react-query";

function Posts() {
  const { data, isLoading, error } = useQuery(["posts"], () =>
    fetch("https://jsonplaceholder.typicode.com/posts").then((res) => res.json())
  );

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

  return (
    <ul>
      {data.map(post => <li key={post.id}>{post.title}</li>)}
    </ul>
  );
}

React Query автоматически валидирует, кеширует и рефетчит данные, если они устарели.

Пример SWR

import useSWR from "swr";

const fetcher = (url) => fetch(url).then(res => res.json());

function Profile() {
  const { data, error, isLoading } = useSWR("https://jsonplaceholder.typicode.com/users/3", fetcher);

  if (isLoading) return <div>Loading...</div>;
  if (error) return <div>Ошибка: {error.message}</div>;

  return <div>Имя пользователя: {data.name}</div>;
}

SWR и React Query очень помогают и с оптимистичным обновлением данных, и с работой offline. Рассмотрите эти инструменты, если ваше приложение достаточно сложное.

Продвинутые техники: отмена, задержка, кеширование запросов

Как отменять запросы

Иногда пользователь быстро покидает компонент, а запрос еще не завершён. Чтобы не обновлять размонтированный компонент, запросы нужно уметь отменять.

Fetch поддерживает AbortController, смотрите, как это работает:

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

  fetch(url, { signal: controller.signal })
    .then(res => res.json())
    .then(setData)
    .catch(e => {
      if (e.name === "AbortError") {
        // Запрос был отменён
      } else {
        setError(e);
      }
    });

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

Использование кеша

Кеширование позволяет уменьшить количество обращений к серверу.

  • React Query/SWR реализуют автоматическое кеширование.
  • Можно реализовать мемоизированный кеш вручную внутри кастомного хука.

Вот простой пример кеша:

const cache = {};

export function useCachedFetch(url) {
  const [data, setData] = useState(cache[url] || null);

  useEffect(() => {
    if (!cache[url]) {
      fetch(url)
        .then(res => res.json())
        .then(json => {
          cache[url] = json;
          setData(json);
        });
    }
  }, [url]);
  return data;
}

Задержка запросов (debouncing)

Если вы делаете запросы на каждом нажатии клавиши (например, поиск), используйте debouncing, чтобы ограничить частоту запросов.

import { useState, useEffect } from "react";

function useDebouncedValue(value, delay) {
  const [debounced, setDebounced] = useState(value);

  useEffect(() => {
    const handler = setTimeout(() => setDebounced(value), delay);

    return () => clearTimeout(handler); // очищаем таймер при новом вводе
  }, [value, delay]);

  return debounced;
}

Теперь отправляйте запрос только после задержки.

Проблемы авторизации и безопасности

Часто API требует авторизации. Обычно используется JWT или session cookie.

Как передавать токены

  • Передавайте accessToken в заголовке Authorization:
fetch("/api/protected", {
  headers: { "Authorization": "Bearer <token>" }
});

В случае axios:

axios.get("/api/protected", {
  headers: { "Authorization": `Bearer ${token}` }
});

Часто удобнее добавить interceptor, который автоматически подставляет токен.

Обновление токена (refresh-token flow)

Если accessToken устарел, можно выполнить обновление токена по refreshToken. Здесь логика строится на ловле ошибки 401 и авто-повторном запросе.

Интеграция API в архитектуру приложения

С ростом проекта полезно отделить логику работы с API в отдельные файлы/модули (services или api). Это облегчает поддержку и повторное использование.

Пример структуры

/src
  /api
    userApi.js
    postsApi.js
  /components
    Users.js
    Posts.js

В файлах userApi.js/ postsApi.js размещайте все функции для работы с определённой сущностью.

// userApi.js
export function fetchUsers() {
  return fetch("/api/users").then(res => res.json());
}

В компоненте используйте:

import { fetchUsers } from "../api/userApi";
// ...
useEffect(() => {
  fetchUsers().then(setUsers);
}, []);

Заключение

В React-приложениях интеграция с API — это ключевой навык для современного фронтенд-разработчика. Я показал вам, как реализовать запросы к API с помощью стандартных средств (fetch), библиотеки axios, продвинутых хуков, а также как работать с библиотеками для автоматизации работы с удаленными источниками данных (React Query, SWR). Мы рассмотрели приемы управления состояниями, обработки ошибок, реализации запросов разных типов и хранения данных, техники отмены, кеширования и авторизации.

Подходите к организации общения с внешними API системно: выносите логику в сервисы, пользуйтесь кастомными и сторонними хуками, не забывайте о безопасности и UX. Это упростит поддержку, ускорит разработку и сделает приложение отзывчивым и удобным для пользователей.

Работа с API - это важный навык, но для создания полноценных приложений требуется умение управлять состоянием и роутингом. Курс Основы React, React Router и Redux Toolkit поможет освоить все аспекты разработки. В первых 3 модулях уже доступно бесплатное содержание — начните погружаться в основы React уже сегодня.

Частозадаваемые технические вопросы по теме статьи и ответы на них

Как автоматически повторить запрос при ошибке соединения?

Используйте дополнительную функцию-обертку для повторных попыток. Например, с fetch:

async function fetchWithRetry(url, attempts = 3) {
  for (let i = 0; i < attempts; i++) {
    try {
      const res = await fetch(url);
      if (!res.ok) throw new Error("Ошибка ответа");
      return await res.json();
    } catch (e) {
      if (i === attempts - 1) throw e; // На последней попытке выбрасываем ошибку
    }
  }
}

Как обрабатывать массив параллельных запросов и ждать их выполнения?

Можно использовать Promise.all:

const urls = ["/api/1", "/api/2"];
Promise.all(urls.map(url => fetch(url).then(r => r.json())))
  .then(results => {
    // results — массив результатов
  });

Можно ли инициировать запрос до маунта компонента?

В React запросы обычно делают внутри useEffect после маунта. Запускать сетевые операции до маунта не рекомендуется, так как компонент еще не существует в DOM и не сможет обновить свое состояние.

Как обрабатывать загрузку файлов через API?

Используйте FormData:

const formData = new FormData();
formData.append("file", file);

fetch("/api/upload", {
  method: "POST",
  body: formData
});

Как повторно использовать полученные JWT-токены в нескольких запросах?

Рекомендуется хранить токен в памяти (например, в Context или Zustand). Для axios настройте interceptor:

axios.interceptors.request.use(config => {
  config.headers["Authorization"] = `Bearer ${token}`;
  return config;
});
Стрелочка влевоРабота с Redux в React-приложенииИспользование Expo для разработки на ReactСтрелочка вправо

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

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

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

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

Все гайды по React

Открыть базу знаний

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

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

React и Redux Toolkit

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

TypeScript с нуля

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

Next.js - с нуля

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

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