Урок 14: useEffect для работы с API в React

Мы уже прошли немалый путь, научились создавать компоненты, управлять состоянием с помощью useState и даже подружились с хуком useEffect для выполнения побочных эффектов.

Сегодня мы поднимем наши навыки на совершенно новый уровень. Мы будем учиться «разговаривать» с внешним миром! Подавляющее большинство современных веб-приложений не живут в вакууме. Они получают данные из социальных сетей, показывают актуальные новости, загружают списки товаров для интернет-магазинов. И всё это происходит путем общения с сервером. Этот урок посвящен именно этому, использованию useEffect для работы с API. Это один из самых часто используемых паттернов в React-разработке, так что давайте сосредоточимся.

Для чего нужен useEffect для работы с API?

Перед тем как погрузиться в код, давайте четко осознаем, почему мы вообще используем useEffect для подобных задач. Вспомним основное предназначение этого хука: он позволяет нам выполнять побочные эффекты, которые не могут быть выполнены во время обычного рендера компонента. Запрос к API, это классический пример побочного эффекта.

Представьте, что ваш компонент должен отобразить список пользователей. Где взять этот список? Он хранится на удаленном сервере. Запрос к этому серверу, это асинхронная операция (то есть она занимает какое-то время) и она имеет «побочные» последствия для компонента: после успешного выполнения запроса мы получим данные и обновим состояние нашего компонента, что вызовет повторный рендер.

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

useEffect дает нам этот контроль. Мы можем указать, что эффект (наш запрос к API) должен выполняться только при определенных условиях, чаще всего, только один раз, когда компонент монтируется в DOM. Это идеально подходит для первоначальной загрузки данных.

Использование fetch или axios внутри useEffect для получения данных

Теперь перейдем к самой практике. В JavaScript есть два самых популярных способа делать HTTP-запросы, нативный fetch и библиотека axios. Мы рассмотрим оба, чтобы вы могли выбрать инструмент, который вам больше по душе.

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

fetch это современный нативный API браузера для выполнения сетевых запросов. Он основан на Promises (Обещаниях), что делает работу с ним достаточно удобной.

Базовая структура нашего компонента с использованием fetch внутри useEffect будет выглядеть так:

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

function UserList() {
  // 1. Создаем состояние для хранения данных
  const [users, setUsers] = useState([]);
  // 2. Создаем состояния для индикации загрузки и ошибок
  const [isLoading, setIsLoading] = useState(true);
  const [error, setError] = useState(null);

  // 3. Используем useEffect для выполнения запроса
  useEffect(() => {
    // Эта функция будет запущена после монтирования компонента
    const fetchData = async () => {
      try {
        setIsLoading(true);
        // Выполняем GET-запрос к API
        const response = await fetch('https://jsonplaceholder.typicode.com/users');
        
        // Проверяем, успешен ли ответ (статус 200-299)
        if (!response.ok) {
          throw new Error(`Ошибка HTTP: статус ${response.status}`);
        }
        
        // Парсим JSON из ответа
        const data = await response.json();
        
        // Обновляем состояние с полученными данными
        setUsers(data);
        // Сбрасываем ошибку, если запрос успешен
        setError(null);
      } catch (err) {
        // Если что-то пошло не так, ловим ошибку
        setError(err.message);
        setUsers([]); // Очищаем пользователей на случай ошибки
      } finally {
        // Выключаем индикатор загрузки в любом случае
        setIsLoading(false);
      }
    };

    fetchData();
  }, []); // 4. Пустой массив зависимостей = эффект только при монтировании

  // 5. Рендерим компонент в зависимости от состояния
  if (isLoading) {
    return <div>Загружаем список пользователей...</div>;
  }

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

  return (
    <div>
      <h1>Список пользователей</h1>
      <ul>
        {users.map(user => (
          <li key={user.id}>{user.name} ({user.email})</li>
        ))}
      </ul>
    </div>
  );
}

export default UserList;

Давайте разберем этот код по шагам:

  1. Создание состояния. Мы определяем три состояния: users для данных, isLoading для отображения процесса загрузки и error для обработки возможных сбоев. Это хорошая практика, которая делает интерфейс отзывчивым и надежным.

  2. useEffect и асинхронность. Обратите внимание, мы не можем сделать саму функцию-эффект асинхронной (использовать async прямо в колбэке useEffect). Потому что useEffect должен возвращать либо функцию очистки, либо undefined. А async-функция всегда возвращает Promise. Поэтому мы создаем внутри эффекта асинхронную функцию fetchData и сразу же вызываем ее.

  3. Обработка ответа. Мы используем await для ожидания ответа от fetch. Затем проверяем response.ok.  Это важно, так как fetch не считает ошибки HTTP (например, 404 или 500) сетевыми ошибками и не отклоняет Promise в таких случаях. Если статус не ок, мы бросаем свою ошибку.

  4. Пустой массив зависимостей. Ключевой момент! Передавая пустой массив [] вторым аргументом в useEffect, мы говорим React: «запусти этот эффект только один раз, после первого рендера». Это именно то, что нужно для первоначальной загрузки данных.

  5. Условный рендеринг. В зависимости от состояний isLoading и error мы рендерим разный UI. Сначала показываем индикатор загрузки, затем сообщение об ошибке или, наконец, сам список пользователей.

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

axios это популярная сторонняя библиотека для HTTP-запросов. У нее есть несколько преимуществ перед fetch «из коробки»:

  • Преобразует JSON-ответ автоматически.

  • Более удобный обработчик ошибок (он автоматически отклоняет Promise при статусах 4xx/5xx).

  • Межплатформенность (работает и в браузере и в Node.js).

  • Более богатый API (например, отмена запросов).

Сначала установите ее в ваш проект: npm install axios

Теперь перепишем наш компонент с использованием axios:

jsx
import React, { useState, useEffect } from 'react';
import axios from 'axios'; // Импортируем axios

function UserListWithAxios() {
  const [users, setUsers] = useState([]);
  const [isLoading, setIsLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    // Создаем функцию для отмены запроса (опционально, но полезно)
    const cancelTokenSource = axios.CancelToken.source();

    const fetchData = async () => {
      try {
        setIsLoading(true);
        // Выполняем запрос с axios
        const response = await axios.get('https://jsonplaceholder.typicode.com/users', {
          cancelToken: cancelTokenSource.token // Передаем токен для возможности отмены
        });
        
        // Данные уже автоматически распаршены в response.data
        setUsers(response.data);
        setError(null);
      } catch (err) {
        // axios бросает ошибку для статусов 4xx/5xx
        if (axios.isCancel(err)) {
          console.log('Запрос был отменен', err.message);
        } else {
          setError(err.message);
        }
      } finally {
        setIsLoading(false);
      }
    };

    fetchData();

    // Функция очистки: отменяем запрос при размонтировании компонента
    return () => {
      cancelTokenSource.cancel('Запрос прерван из-за размонтирования компонента.');
    };
  }, []); // Все так же, эффект только при монтировании

  // Рендерим так же, как и в примере с fetch
  if (isLoading) return <div>Загружаем список пользователей...</div>;
  if (error) return <div>Ошибка: {error}</div>;

  return (
    <div>
      <h1>Список пользователей (через Axios)</h1>
      <ul>
        {users.map(user => (
          <li key={user.id}>{user.name} ({user.email})</li>
        ))}
      </ul>
    </div>
  );
}

export default UserListWithAxios;

Что изменилось с axios?

  1. Автопарсинг JSON. Нам не нужно вручную вызывать response.json(). Данные доступны сразу в response.data.

  2. Обработка ошибок. axios автоматически отвергает Promise, если статус ответа в диапазоне 400 или 500, поэтому наша логика в catch сработает и для ошибок HTTP.

  3. Отмена запроса. Мы добавили продвинутую, но очень важную функцию, отмену запроса. Мы создаем CancelToken и возвращаем функцию очистки из useEffect. Если пользователь покинет страницу до завершения запроса, React вызовет эту функцию очистки и запрос будет прерван. Это предотвращает потенциальные ошибки, когда мы пытаемся обновить состояние уже размонтированного компонента.

Обработка состояний загрузки и ошибок

Ты уже заметил, что в наших примерах мы активно используем состояния isLoading и error. Это не просто «так принято», это фундамент хорошего пользовательского опыта (UX).

Представьте, что пользователь нажимает на ссылку и экран просто замирает на несколько секунд. Он не понимает, работает ли приложение или сломалось. Индикатор загрузки дает немедленную визуальную обратную связь: «Данные загружаются, подожди немного».

Аналогично, если сервер вернул ошибку, молча проигнорировать ее. Это худшее, что можно сделать. Пользователь будет в неведении. Явное сообщение об ошибке, даже простое, как в нашем примере, информирует его о проблеме. В реальном приложении ты можешь сделать это сообщение более красивым и, возможно, добавить кнопку для повторной попытки.

Всегда думай о трех возможных состояниях любого компонента, который загружает данные:

  1. Загрузка (Loading). Показываем спиннер, скелетон или сообщение.

  2. Успех (Success). Показываем сами данные.

  3. Ошибка (Error). Показываем сообщение об ошибке.

Частые ошибки и лучшие практики

На пути к мастерству работы с API в React все наступают на одни и те же грабли. Давай я укажу на них, чтобы ты смог их обойти.

  1. «Пропущенный» массив зависимостей. Самая частая ошибка новичков, это забыть добавить массив зависимостей в useEffect. Это приводит к бесконечным циклам запросов. Всегда задавай себе вопрос: «Когда должен повторно запускаться этот эффект?» Если для первоначальной загрузки массив пустой [].

  2. Игнорирование функции очистки. Мы уже видели ее в примере с axios. Если твой эффект запускает какие-то асинхронные операции (запросы, таймеры, подписки), всегда возвращай функцию очистки, чтобы отменить их при размонтировании компонента. Это предотвращает утечки памяти и попытки обновить «несуществующий» компонент.

  3. Неправильная обработка ошибок. Не полагайся слепо на fetch или axios. Всегда оборачивай асинхронный код в try/catch и предусматривай состояние для ошибки в своем компоненте.

  4. «Закрытие в прошлом» (Stale Closure). Иногда тебе может понадобиться использовать какое-то состояние или пропс внутри useEffect. Если твой эффект зависит от этих значений, ты должен указать их в массиве зависимостей. Иначе эффект будет «видеть» их старые, начальные значения.

Практические задачи для закрепления

Вот несколько задач, которые помогут тебе закрепить материал. Не пропускай их.

Задача 1: Базовый компонент постов

Создай компонент PostList, который загружает и отображает список постов с API https://jsonplaceholder.typicode.com/posts. Реализуй индикатор загрузки и обработку ошибок. Используй нативный fetch.

Задача 2: Детали пользователя

Создай компонент UserDetail, который получает userId через пропсы. При изменении userId компонент должен делать запрос к https://jsonplaceholder.typicode.com/users/{userId} и показывать информацию о выбранном пользователе (имя, email, телефон). Не забудь добавить userId в массив зависимостей useEffect!

Задача 3: Улучшенный обработчик с axios

Перепиши компонент из Задачи 1, используя axios. Добавь функцию отмены запроса в функцию очистки useEffect.

Пример для Задачи 2:

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

function UserDetail({ userId }) {
  const [user, setUser] = useState(null);
  const [isLoading, setIsLoading] = useState(false);
  const [error, setError] = useState(null);

  useEffect(() => {
    // Сбрасываем ошибку и пользователя при смене userId
    setError(null);
    setUser(null);

    // Если userId не передан, не делаем запрос
    if (!userId) return;

    const cancelTokenSource = axios.CancelToken.source();

    const fetchUser = async () => {
      setIsLoading(true);
      try {
        const response = await axios.get(`https://jsonplaceholder.typicode.com/users/${userId}`, {
          cancelToken: cancelTokenSource.token
        });
        setUser(response.data);
      } catch (err) {
        if (!axios.isCancel(err)) {
          setError(err.message);
        }
      } finally {
        setIsLoading(false);
      }
    };

    fetchUser();

    return () => cancelTokenSource.cancel();
  }, [userId]); // Зависимость от userId! Эффект запустится заново при его изменении.

  if (!userId) return <div>Выберите пользователя...</div>;
  if (isLoading) return <div>Загружаем данные пользователя...</div>;
  if (error) return <div>Ошибка: {error}</div>;
  if (!user) return null;

  return (
    <div>
      <h2>{user.name}</h2>
      <p><strong>Email:</strong> {user.email}</p>
      <p><strong>Phone:</strong> {user.phone}</p>
      <p><strong>Company:</strong> {user.company?.name}</p>
    </div>
  );
}

export default UserDetail;

 Ты только что освоил один из краеугольных камней современной React-разработки. Умение работать с API через useEffect открывает перед тобой двери к созданию полноценных, динамических приложений. Это был насыщенный урок, не бойся возвращаться к нему и перечитывать сложные моменты.

В следующих уроках мы поговорим о правилах хуков и о том, как создавать свои собственные хуки, чтобы повторно использовать логику (например, логику загрузки данных) между разными компонентами.

Не забывай задавать вопросы в комментариях, если они возникли. Хочешь освоить React на практике?

Полный курс с уроками по React для начинающих

Поделиться статьей:
Поддержать автора блога

Поддержка автора осуществляется с помощью специальной формы ниже, предоставленной сервисом «ЮMoney». Все платёжные операции выполняются на защищённой странице сервиса, что обеспечивает их корректность и полную безопасность.

Персональные рекомендации
Оставить комментарий