Skip to main content

useEffect і асинхронний код

1. React-компонент має бути чистою функцією

Компонент у React - це чиста функція, яка:

  • приймає вхідні дані (props);
  • повертає опис UI (JSX);
  • не повинна мати побічних ефектів (наприклад, мережевих запитів, таймерів, логів тощо).

Чиста функція - це функція, яка за однакових вхідних даних завжди повертає той самий результат, і не змінює зовнішній стан.

Асинхронні запити (fetch, axios, WebSocket тощо) - це побічні ефекти, вони взаємодіють із зовнішнім світом (мережею, API, диском, браузером).

Тому React забороняє виконувати побічні ефекти під час рендера (у тілі функції-компонента), щоб не порушувати принцип передбачуваності.


2. useEffect() запускається після рендера

Коли React викликає компонент, він:

  1. викликає функцію -> отримує JSX (чистий результат);
  2. відображає цей JSX у DOM;
  3. і тільки потім - виконує побічні ефекти, зареєстровані через useEffect().

Це ідеальне місце для асинхронних операцій, тому що:

  • UI вже змонтований (можна показати "Loading...");
  • можна безпечно оновити стан, не впливаючи на поточний рендер.

Приклад:

javascript
useEffect(() => { fetch('/api/user') .then(res => res.json()) .then(data => setUser(data)); }, []);

Тут React спочатку покаже компонент з Loading..., а після завершення запиту оновить state і відрендерить оновлений UI.


3. Асинхронний код не можна писати прямо в компоненті

Якщо спробувати:

javascript
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 приймає другий аргумент - масив залежностей:

javascript
useEffect(() => { fetch(`/api/users/${id}`) .then(r => r.json()) .then(setUser); }, [id]);

Таким чином можна:

  • викликати запит тільки один раз ([]);
  • або тільки при зміні конкретних даних ([id]).

Це дає повний контроль над тим, коли саме виконується запит. Без useEffect у тебе не було б такого керування - запити йшли б на кожен рендер.


5. Можливість очищення (cleanup)

Якщо компонент розмонтується під час завантаження даних, useEffect дозволяє очистити ефект, щоб не оновлювати state у розмонтованому компоненті:

javascript
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 RenderingReact може переривати і повторювати рендер без проблем

Коротко:

Асинхронні запити виконуються всередині useEffect(), тому що це єдине місце, де React дозволяє побічні ефекти, після завершення рендера, з контролем залежностей і можливістю очищення.

Коротка відповідь

Для співбесіди
Premium

Коротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.