useEffect і асинхронний код
1. React-компонент має бути чистою функцією
Компонент у React - це чиста функція, яка:
- приймає вхідні дані (
props); - повертає опис UI (
JSX); - не повинна мати побічних ефектів (наприклад, мережевих запитів, таймерів, логів тощо).
Чиста функція - це функція, яка за однакових вхідних даних завжди повертає той самий результат, і не змінює зовнішній стан.
Асинхронні запити (fetch, axios, WebSocket тощо) - це побічні ефекти,
вони взаємодіють із зовнішнім світом (мережею, API, диском, браузером).
Тому React забороняє виконувати побічні ефекти під час рендера (у тілі функції-компонента), щоб не порушувати принцип передбачуваності.
2. useEffect() запускається після рендера
Коли React викликає компонент, він:
- викликає функцію -> отримує JSX (чистий результат);
- відображає цей JSX у DOM;
- і тільки потім - виконує побічні ефекти, зареєстровані через
useEffect().
Це ідеальне місце для асинхронних операцій, тому що:
- UI вже змонтований (можна показати "Loading...");
- можна безпечно оновити стан, не впливаючи на поточний рендер.
Приклад:
useEffect(() => {
fetch('/api/user')
.then(res => res.json())
.then(data => setUser(data));
}, []);Тут React спочатку покаже компонент з Loading...,
а після завершення запиту оновить state і відрендерить оновлений UI.
3. Асинхронний код не можна писати прямо в компоненті
Якщо спробувати:
function User() {
const res = await fetch('/api/user'); // Помилка
const data = await res.json();
return <div>{data.name}</div>;
}React не вміє чекати await у клієнтських компонентах (окрім Server Components).
Він очікує, що компонент синхронно поверне JSX, а не проміс.
Тому це викличе помилку.
useEffect() якраз дозволяє відкласти виконання таких асинхронних операцій
доти, доки React закінчить фазу рендера.
4. Контроль залежностей
useEffect приймає другий аргумент - масив залежностей:
useEffect(() => {
fetch(`/api/users/${id}`)
.then(r => r.json())
.then(setUser);
}, [id]);Таким чином можна:
- викликати запит тільки один раз (
[]); - або тільки при зміні конкретних даних (
[id]).
Це дає повний контроль над тим, коли саме виконується запит.
Без useEffect у тебе не було б такого керування - запити йшли б на кожен рендер.
5. Можливість очищення (cleanup)
Якщо компонент розмонтується під час завантаження даних,
useEffect дозволяє очистити ефект, щоб не оновлювати state у розмонтованому компоненті:
useEffect(() => {
let active = true;
fetch('/api/data')
.then(r => r.json())
.then(data => {
if (active) setData(data);
});
return () => { active = false }; // cleanup
}, []);Без useEffect реалізувати це коректно було б неможливо.
6. React 18 і асинхронні рендери
З React 18 з'явилося асинхронне відображення (Concurrent Rendering). У ньому рендер може бути:
- призупинений,
- перерваний,
- повторений.
Якби асинхронні запити виконувалися під час рендера, React не зміг би безпечно їх перервати - виникли б гонки даних, витоки і дублювання запитів.
useEffect вирішує це - він виконується після успішної commit-фази,
тобто тільки тоді, коли компонент справді відображений.
ПІДСУМОК
| Що відбувається | Чому це важливо |
|---|---|
| Компонент має бути чистою функцією | Асинхронні операції - це побічні ефекти |
useEffect() запускається після рендера | Безпечно виконувати запити |
Другий аргумент [] керує частотою | Можна уникнути зайвих запитів |
| Можна очистити ефект | Без витоків і гонок даних |
| Сумісно з Concurrent Rendering | React може переривати і повторювати рендер без проблем |
Коротко:
Асинхронні запити виконуються всередині
useEffect(), тому що це єдине місце, де React дозволяє побічні ефекти, після завершення рендера, з контролем залежностей і можливістю очищення.
Коротка відповідь
Для співбесідиКоротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.