fetch: Робота зі стримами (Streams) та обробка HTTP статусів.axios як галузевий стандарт: Інтерсептори (Interceptors) та автоматична серіалізація.AbortController.useFetch.React Query, SWR) замість useEffect.Методи отримання даних з сервера за допомогою fetch та axios. Обробка станів завантаження (loading) та помилок (error). Патерни інтеграції API запитів у хук useEffect та створення кастомних хуків для фетчингу даних.
Сучасні SPA-додатки є лише “обгорткою” (Клієнтом) для відображення даних. Всі реальні бізнес-операції (оплата, збереження інформації, розрахунки) виконуються на зовнішніх серверах (Бекенді). Взаємодія між клієнтом і сервером у браузері завжди відбувається асинхронно, оскільки передача даних по мережі може тривати від мілісекунд до десятків секунд. Щоб не блокувати єдиний потік виконання JavaScript (Main Thread) і дозволяти користувачам продовжувати взаємодію з інтерфейсом (скролити, натискати кнопки) під час очікування відповіді, інженери застосовують потужні асинхронні патерни. У цій лекції ми розберемо інтеграцію сторонніх API у життєвий цикл React-компонентів, розглядаючи всі підводні камені, від витоків пам’яті до “гонитви запитів”.
Оскільки веб-браузери виділяють лише один головний потік (Thread) для малювання UI та виконання JavaScript, будь-яка мережева дія (яка займає час) заблокувала б програму.
Тому fetch працює на базі Promise (Промісів) — об’єктів, які представляють результат успішної або невдалої асинхронної операції в майбутньому.
У світі React ми зобов’язані запам’ятати архітектурне правило: Рендер-функція компонента завжди МАЄ БУТИ СИНХРОННОЮ. Ми не можемо змусити React “чекати” на завершення запиту під час повернення JSX.
// ❌ АРХІТЕКТУРНА КАТАСТРОФА (Компонент не може бути async)
const UserProfile = async () => {
const data = await fetch("/api/user"); // Це зламає всю гілку Virtual DOM
return <div>{data.name}</div>;
};
Мережеві запити завжди делегуються на хук useEffect, який виконається асинхронно, ВЖЕ ПІСЛЯ того як компонент вперше відмалюється (з порожніми або “Loading” даними).
fetch: Анатомія та проблемиfetch — це стандарт (вбудований у window), який обіцяє базовий функціонал, але покладає всю подальшу роботу з обробки на розробника.
fetch не чекає на завантаження всього тіла відповіді. Перший Promise резолвиться, як тільки браузер отримує заголовки (Headers) сервера. Тіло є об’єктом ReadableStream. Щоб отримати JSON, ми повинні прочитати цей стрім (через метод .json()), який повертає ще один Promise.
fetch("https://api.example.com/data")
// 1-й крок: розшифровка заголовків
.then((response) => {
// УВАГА: fetch не вважає статуси 404 або 500 "помилкою" (catch).
// Для fetch помилкою є лише відсутність інтернету!
if (!response.ok) throw new Error("HTTP Status Error");
return response.json();
})
// 2-й крок: парсинг тіла JSON
.then((data) => console.log(data));
axios як галузевий стандартДля усунення незручностей нативного fetch (ручна обробка 400-х статусів, обов’язкове дописування headers: {'Content-Type': 'application/json'} та подвійний парсинг response.json()), Enterprise-системи масово використовують бібліотеку axios.
catch).// Налаштування Інтерсептора (один раз в корені додатку)
axios.interceptors.request.use((config) => {
const token = localStorage.getItem("token");
if (token) config.headers.Authorization = `Bearer ${token}`;
return config;
});
// У компоненті все виглядає надзвичайно чисто:
const fetchData = async () => {
try {
const { data } = await axios.get("/api/users");
setUsers(data);
} catch (error) {
console.error("Server error", error.message);
}
};
Будь-яка повноцінна взаємодія з мережею в React потребує 3-х незалежних змінних стану (States) для покриття всіх сценаріїв взаємодії користувача з UI.
const UserList = () => {
const [data, setData] = useState(null); // Стан №1: Самі Дані
const [isLoading, setIsLoading] = useState(true); // Стан №2: Індикатор завантаження
const [error, setError] = useState(null); // Стан №3: Текст помилки
useEffect(() => {
// Асинхронна функція всередині useEffect
const fetchUsers = async () => {
setIsLoading(true);
setError(null);
try {
const response = await axios.get("/api/users");
setData(response.data);
} catch (err) {
setError(err.message || "Сталася помилка");
} finally {
// Finally виконається завжди: як при успіху, так і при помилці
setIsLoading(false);
}
};
fetchUsers();
}, []);
// Рендеринг (Зверніть увагу на Early Returns!)
if (isLoading) return <Spinner />;
if (error) return <AlertBox message={error} />;
return (
<ul>
{data.map((user) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
};
Цей патерн є еталоном (Best Practice). Ми явно повідомляємо користувачу (через UI), що зараз відбувається “спілкування” з сервером.
AbortControllerНайпоширеніший (і найстрашніший) баг з асинхронністю виникає при частих кліках або використанні рядка пошуку (Search Input).
Сценарій катастрофи:
setData() від Запиту №1.Вирішення через Cleanup + AbortController API: Ми повинні “вбивати” (скасовувати) старі HTTP-запити, коли ефект спрацьовує наново.
useEffect(() => {
// 1. Створюємо нативний контролер
const controller = new AbortController();
const searchProduct = async () => {
try {
// 2. Прив'язуємо "сигнал знищення" до запиту
const response = await fetch(`/api/search?q=${query}`, {
signal: controller.signal,
});
// ...
} catch (err) {
if (err.name === "AbortError") {
console.log("Запит успішно скасовано. Це не баг.");
}
}
};
searchProduct();
// 3. Cleanup функція
return () => {
// Коли useEffect викликається знову (при введенні "Б"),
// React виконує ТУТ controller.abort(), що перериває запит на "А" в польоті!
controller.abort();
};
}, [query]);
useFetchОписувати Тріаду станів (loading/error/data) і AbortController в кожному компоненті — це болісне дублювання коду (порушення принципу DRY).
Професійні інженери абстрагують цю логіку в свій власний (Custom) Хук.
// useFetch.js
import { useState, useEffect } from "react";
export const useFetch = (url) => {
const [data, setData] = useState(null);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const controller = new AbortController();
setIsLoading(true);
fetch(url, { signal: controller.signal })
.then((res) => {
if (!res.ok) throw new Error("Network Error");
return res.json();
})
.then((data) => setData(data))
.catch((err) => {
if (err.name !== "AbortError") setError(err.message);
})
.finally(() => setIsLoading(false));
return () => controller.abort();
}, [url]);
return { data, isLoading, error }; // Повертаємо тріаду
};
Застосування в компоненті зводиться до одного лаконічного рядка:
const { data: users, isLoading, error } = useFetch("/api/users");
Як ми бачимо, використання useEffect для стягування даних вимагає чимало інженерних зусиль (обробка Race Conditions, менеджмент loading).
Крім того, кожне використання хука робить новий запит (дані НЕ кешуються між переходами по сторінках).
У сучасному Enterprise React для цього використовують так звані State Management бібліотеки серверного стану, найвідомішими серед яких є React Query (TanStack Query) та SWR (від компанії Vercel).
Їхня філософія:
useEffect. Вони надають готові хуки, що автоматично повертають стани./api/user, React Query зробить фізичний запит на сервер ТІЛЬКИ 1 РАЗ і роздасть відповідь усім трьом).Код за допомогою React Query (Ознайомче):
import { useQuery } from "@tanstack/react-query";
const fetchUserList = () => axios.get("/api/users").then((res) => res.data);
const Component = () => {
// "users" - це унікальний ключ кешування
const { data, isLoading, isError } = useQuery({
queryKey: ["users"],
queryFn: fetchUserList,
});
// ...
};
useEffect.fetch є легкою абстракцією над XMLHttpRequest, який вимагає ручного парсингу Stream-відповідей (.json()) та ручної перевірки статусів res.ok. Галузевий стандарт axios цілком ховає під капот ці проблеми та пропонує інтерсептори (middlewares).AbortController, який “вбиває” повільні застарілі запити, що розсинхронізовуються з поточним стейтом.useFetch), а в просунутих продакшн-середовищах — перевести цей процес на спеціалізовані клієнтські кешуючі системи, такі як TanStack React Query.useEffect)?fetch та бібліотеки axios у ситуації, коли бекенд-сервер повертає HTTP статус помилки 404 Not Found. Як саме спрацює блок try/catch у обох випадках?data, isLoading, error) для якісного користувацького досвіду (UX Design)?useEffect + useState виконує отримання даних абсолютно коректно?