Мы уже прошли немалый путь, научились создавать компоненты, управлять состоянием с помощью 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 будет выглядеть так:
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;
Давайте разберем этот код по шагам:
-
Создание состояния. Мы определяем три состояния:
usersдля данных,isLoadingдля отображения процесса загрузки иerrorдля обработки возможных сбоев. Это хорошая практика, которая делает интерфейс отзывчивым и надежным. -
useEffect и асинхронность. Обратите внимание, мы не можем сделать саму функцию-эффект асинхронной (использовать
asyncпрямо в колбэкеuseEffect). Потому чтоuseEffectдолжен возвращать либо функцию очистки, либоundefined. Аasync-функция всегда возвращает Promise. Поэтому мы создаем внутри эффекта асинхронную функциюfetchDataи сразу же вызываем ее. -
Обработка ответа. Мы используем
awaitдля ожидания ответа отfetch. Затем проверяемresponse.ok. Это важно, так какfetchне считает ошибки HTTP (например, 404 или 500) сетевыми ошибками и не отклоняет Promise в таких случаях. Если статус не ок, мы бросаем свою ошибку. -
Пустой массив зависимостей. Ключевой момент! Передавая пустой массив
[]вторым аргументом вuseEffect, мы говорим React: «запусти этот эффект только один раз, после первого рендера». Это именно то, что нужно для первоначальной загрузки данных. -
Условный рендеринг. В зависимости от состояний
isLoadingиerrorмы рендерим разный UI. Сначала показываем индикатор загрузки, затем сообщение об ошибке или, наконец, сам список пользователей.
Использование axios
axios это популярная сторонняя библиотека для HTTP-запросов. У нее есть несколько преимуществ перед fetch «из коробки»:
-
Преобразует JSON-ответ автоматически.
-
Более удобный обработчик ошибок (он автоматически отклоняет Promise при статусах 4xx/5xx).
-
Межплатформенность (работает и в браузере и в Node.js).
-
Более богатый API (например, отмена запросов).
Сначала установите ее в ваш проект: npm install axios
Теперь перепишем наш компонент с использованием axios:
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?
-
Автопарсинг JSON. Нам не нужно вручную вызывать
response.json(). Данные доступны сразу вresponse.data. -
Обработка ошибок.
axiosавтоматически отвергает Promise, если статус ответа в диапазоне 400 или 500, поэтому наша логика вcatchсработает и для ошибок HTTP. -
Отмена запроса. Мы добавили продвинутую, но очень важную функцию, отмену запроса. Мы создаем
CancelTokenи возвращаем функцию очистки изuseEffect. Если пользователь покинет страницу до завершения запроса, React вызовет эту функцию очистки и запрос будет прерван. Это предотвращает потенциальные ошибки, когда мы пытаемся обновить состояние уже размонтированного компонента.
Обработка состояний загрузки и ошибок
Ты уже заметил, что в наших примерах мы активно используем состояния isLoading и error. Это не просто «так принято», это фундамент хорошего пользовательского опыта (UX).
Представьте, что пользователь нажимает на ссылку и экран просто замирает на несколько секунд. Он не понимает, работает ли приложение или сломалось. Индикатор загрузки дает немедленную визуальную обратную связь: «Данные загружаются, подожди немного».
Аналогично, если сервер вернул ошибку, молча проигнорировать ее. Это худшее, что можно сделать. Пользователь будет в неведении. Явное сообщение об ошибке, даже простое, как в нашем примере, информирует его о проблеме. В реальном приложении ты можешь сделать это сообщение более красивым и, возможно, добавить кнопку для повторной попытки.
Всегда думай о трех возможных состояниях любого компонента, который загружает данные:
-
Загрузка (Loading). Показываем спиннер, скелетон или сообщение.
-
Успех (Success). Показываем сами данные.
-
Ошибка (Error). Показываем сообщение об ошибке.
Частые ошибки и лучшие практики
На пути к мастерству работы с API в React все наступают на одни и те же грабли. Давай я укажу на них, чтобы ты смог их обойти.
-
«Пропущенный» массив зависимостей. Самая частая ошибка новичков, это забыть добавить массив зависимостей в
useEffect. Это приводит к бесконечным циклам запросов. Всегда задавай себе вопрос: «Когда должен повторно запускаться этот эффект?» Если для первоначальной загрузки массив пустой[]. -
Игнорирование функции очистки. Мы уже видели ее в примере с
axios. Если твой эффект запускает какие-то асинхронные операции (запросы, таймеры, подписки), всегда возвращай функцию очистки, чтобы отменить их при размонтировании компонента. Это предотвращает утечки памяти и попытки обновить «несуществующий» компонент. -
Неправильная обработка ошибок. Не полагайся слепо на
fetchилиaxios. Всегда оборачивай асинхронный код вtry/catchи предусматривай состояние для ошибки в своем компоненте. -
«Закрытие в прошлом» (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:
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 на практике?
Поддержка автора осуществляется с помощью специальной формы ниже, предоставленной сервисом «ЮMoney». Все платёжные операции выполняются на защищённой странице сервиса, что обеспечивает их корректность и полную безопасность.


