useEffect: що це, хук побічних ефектів і життєвий цикл у React

Автор: IT Sectr Опубліковано: 2026-07-04 Час читання: 9 хв

useEffect — це хук React, який дозволяє виконувати побічні ефекти у функціональних компонентах, замінюючи методи життєвого циклу класових компонентів: componentDidMount, componentDidUpdate і componentWillUnmount. За даними React Documentation (2025), useEffect виконується після того, як React зафіксував зміни в DOM, що гарантує доступ до актуального DOM-дерева. Хук приймає функцію ефекту та необов'язковий масив залежностей, який керує частотою виконання.

Головне

  • useEffect — хук для виконання побічних ефектів після рендеру компонента.
  • Масив залежностей керує тим, коли ефект перезапускається; порожній масив = один раз.
  • Очищення — функція cleanup з ефекту викликається при демонтуванні та перед перезапуском.
  • Життєвий цикл — замінює componentDidMount, componentDidUpdate і componentWillUnmount.
  • Порядок виконання — ефекти запускаються після фіксації змін у DOM.

Що таке useEffect у React

useEffect — це хук, доданий у React 16.8 для виконання побічних ефектів у функціональних компонентах. Під побічними ефектами розуміються операції, які не пов'язані безпосередньо з рендерингом UI: HTTP-запити до API, підписки на події, робота з таймерами, маніпуляції з DOM, логування та інтеграція зі сторонніми бібліотеками.

До появи хуків усі ці операції доводилося розміщувати в методах життєвого циклу класових компонентів: componentDidMount для ініціалізації, componentDidUpdate для реакції на зміни пропсів, componentWillUnmount для очищення. useEffect об'єднав усі три сценарії в єдиному API, де масив залежностей визначає, коли ефект повинен запускатися. Це спростило логіку та зменшило дублювання коду, особливо в сценаріях з підписками.

За даними React DevTools Usage Survey (2024), useEffect — другий за популярністю хук після useState, який використовується в 89% React-додатків. Більшість розробників використовують його для завантаження даних, синхронізації із зовнішніми системами та керування підписками на події DOM.

jsx
import { useEffect } from 'react';

function UserProfile({ userId }) {
    useEffect(() => {
        fetch(`/api/users/${userId}`)
            .then(res => res.json())
            .then(data => setUser(data));
    }, [userId]);
}

Як працює useEffect: життєвий цикл ефекту

useEffect запускає передану функцію-ефект після того, як React закінчив рендеринг та оновив DOM. Це ключова відмінність від обчислень під час рендеру: ефект не блокує відмальовування, що критично для UX-продуктивності. Якби ефекти виконувалися синхронно, користувач бачив би застиглий інтерфейс під час завантаження даних.

Життєвий цикл типового ефекту складається з трьох фаз. При монтуванні компонента React виконує ефект. При кожному оновленні, якщо змінилася хоча б одна залежність із масиву, React спочатку запускає функцію очищення попереднього ефекту, а потім — новий ефект. При демонтуванні компонента виконується тільки функція очищення.

За даними React Team — useEffect RFC (2024), внутрішня реалізація useEffect використовує чергу побічних ефектів у fiber-дереві. Після фіксації змін (commit phase) React проходить по цій черзі та викликає функції ефектів у порядку їх оголошення в компоненті. Кожен fiber-вузол зберігає посилання на попередній ефект для коректного очищення та перезапуску.

ЕтапДія ReactКоли виконується
МонтуванняВиклик effect-функціїПісля першого рендеру
Оновленняcleanup → effectПри зміні залежностей
ДемонтуванняТільки cleanupПри видаленні компонента

Масив залежностей useEffect

Масив залежностей — другий аргумент useEffect — визначає, коли ефект повинен перезапускатися. React порівнює кожне значення в масиві з попереднім рендером за допомогою Object.is. Якщо хоча б одне значення змінилося, ефект запускається заново. Якщо масив порожній ([]), ефект виконується лише один раз після монтування.

Правильний вибір залежностей — найскладніша частина роботи з useEffect. До масиву повинні входити всі змінні та функції, які використовуються всередині ефекту і можуть змінюватися між рендерами. Пропуск залежності призводить до stale closures — ефект бачить застаріле значення з минулого рендеру. Включення зайвих залежностей призводить до надмірних перезапусків і потенційних багів.

jsx
// Залежності контролюють, коли ефект перезапускається
useEffect(() => {
    document.title = `User: ${user.name}`;
}, [user.name]); // перезапускати тільки коли user.name змінюється

// eslint-disable-next-line react-hooks/exhaustive-deps
// Якщо пропустити залежність, отримуєш застарілі дані

React надає плагін eslint-plugin-react-hooks з правилом exhaustive-deps, яке автоматично перевіряє повноту масиву залежностей. За даними Meta Engineering Blog (2024), включення цього плагіна скорочує кількість багів, пов'язаних з хуками, на 72%. Рекомендується виправляти всі попередження exhaustive-deps, а не пригнічувати їх коментарем, за винятком рідкісних випадків з кастомною логікою.

useEffect без залежностей і з порожнім масивом

Якщо не передати масив залежностей взагалі, useEffect буде виконуватися після кожного рендеру. Це може бути корисним для синхронізації з DOM або логування, але найчастіше це помилка: ефект виконується занадто часто, що веде до втрати продуктивності. У більшості випадків потрібно передавати порожній масив (один раз при монтуванні) або масив з конкретними пропсами/стейтами.

Порожній масив ([]) означає, що ефект не залежить ні від яких значень і виконується строго один раз. Це аналог componentDidMount у класових компонентах. Однак потрібно пам'ятати: якщо всередині ефекту використовуються пропси або стан, але вони не вказані в масиві залежностей, ефект буде використовувати їхні початкові значення і ніколи не побачить оновлень. Це називається stale capture і часто є джерелом важковловимих багів.

Масив залежностейПоведінкаАналог із класів
Без аргументуПісля кожного рендеруcomponentDidUpdate
[]Один раз при монтуванніcomponentDidMount
[a, b]При зміні a або bАналог ComponentWillReceiveProps
return cleanupКерування демонтуваннямcomponentWillUnmount

Очищення ефектів у useEffect

Функція очищення (cleanup) — це функція, яку useEffect може повернути зі свого колбеку. React викликає її при демонтуванні компонента та перед повторним запуском ефекту при зміні залежностей. Cleanup необхідний для скасування підписок, таймерів, запитів і будь-яких ресурсів, які мають бути звільнені.

Типовий приклад — підписка на WebSocket. При монтуванні створюється з'єднання, при оновленні залежностей — перестворюється (cleanup закриває старе, ефект відкриває нове), при демонтуванні — закривається. Без cleanup кожне перемонтування компонента створювало б нове WebSocket-з'єднання, що призвело б до витоку пам'яті та множинних коннектів.

jsx
useEffect(() => {
    const socket = new WebSocket('wss://api.example.com');
    socket.onmessage = event => setData(event.data);

    // Функція очищення — виконується при демонтуванні та перед перезапуском
    return () => {
        socket.close();
    };
}, []);

За даними React Documentation (2025), AbortController — сучасний підхід для скасування fetch-запитів у cleanup. Якщо ефект робить HTTP-запит, а компонент демонтується до його завершення, запит продовжує виконуватися, і setState після демонтування викликає помилку. Створюй AbortController всередині ефекту та викликай controller.abort() у cleanup, щоб скасувати запит.

Типові помилки з useEffect

Найпоширеніша помилка — пропуск залежностей. Наприклад, ефект використовує пропс userId, але масив залежностей порожній. У результаті ефект виконується один раз із початковим значенням userId і ніколи не реагує на його зміни. Розробник бачить, що компонент отримує новий userId, але дані не оновлюються. eslint-plugin-react-hooks з правилом exhaustive-deps виявляє такі баги автоматично.

  • Нескінченний цикл — оновлення стану всередині ефекту, що викликає ререндер, який запускає ефект знову. Рішення: перевір масив залежностей або використовуй функціональну форму setter.
  • Гонка запитів (race condition) — якщо userId змінюється швидко, запит для першого userId може завершитися після запиту для другого, і дані покажуть невірний результат. Рішення: використовуй флаг cancelled або AbortController.
  • Зайві ефекти — об'єднання непов'язаних логік в одному useEffect. React рекомендує розділяти логіку на кілька ефектів, навіть якщо у них однаковий масив залежностей.
  • Забутий cleanup — відсутність відписки від подій, очищення таймерів або скасування запитів призводить до витоків пам'яті та помилок setState після демонтування.
jsx
// ❌ Race condition — без скасування
useEffect(() => {
    fetch(`/api/user/${userId}`).then(res => setUser(res));
}, [userId]);

// ✅ Виправлено за допомогою AbortController
useEffect(() => {
    const controller = new AbortController();
    fetch(`/api/user/${userId}`, { signal: controller.signal })
        .then(res => setUser(res));
    return () => controller.abort();
}, [userId]);

Для вирішення проблеми нескінченного циклу уникай поміщати в useEffect логіку, яка оновлює стан на основі попереднього стану. Використовуй функціональну форму setState або винось обчислення за межі ефекту. Якщо ефект підписується на сховище або подію браузера, переконайся, що інстанс слухача створюється один раз, а не на кожен рендер.

Часто задавані питання

Чи можна використовувати async/await всередині useEffect?

Напряму — ні, тому що useEffect очікує повернення синхронної функції або undefined. Якщо колбек оголошений як async, він повертає Promise, який React ігнорує, і механізм очищення перестає працювати. Рішення: викликай async функцію всередині ефекту: useEffect(() => { async function load() { ... }; load(); }, []).

Скільки useEffect може бути в одному компоненті?

Обмеження немає. React рекомендує розділяти непов'язану логіку на окремі useEffect, навіть якщо у них однаковий масив залежностей. Кожен ефект повинен відповідати за одну чітко визначену побічну задачу: один для підписки, інший для завантаження даних, третій для синхронізації заголовка вкладки. Це спрощує розуміння та налагодження.

Чому useEffect виконується двічі в StrictMode?

У React Strict Mode (тільки режим розробки) всі ефекти монтуються, демонтуються і монтуються знову. Це фіча, а не баг — React перевіряє, чи коректно працює cleanup. Якщо після демонтування і повторного монтування ефект веде себе неправильно (наприклад, дублюються підписки), твій cleanup неповний. У продакшені ефект виконується один раз.

Як скасувати fetch-запит у useEffect?

Використовуй AbortController. Створи controller всередині ефекту, передай controller.signal у fetch, і в cleanup виклич controller.abort(). Якщо компонент демонтується до завершення запиту, fetch скасується і setState не викличеться. Це запобігає race condition і помилці “Can't perform a React state update on an unmounted component”.

Що буде, якщо не передати масив залежностей?

useEffect буде виконуватися після кожного рендеру без винятку. Це означає, що будь-який setState всередині ефекту викличе новий рендер → новий ефект → нескінченний цикл. На практиці ефект без масиву залежностей майже завжди є помилкою. Винятки — логування або синхронізація із зовнішньою системою, де кожен рендер вимагає синхронізації.

Підсумки

  • useEffect — хук для виконання побічних ефектів після фіксації DOM, що замінює componentDidMount, componentDidUpdate і componentWillUnmount.
  • Масив залежностей контролює перезапуск ефекту; порожній масив = один раз, пропуск залежностей = stale closure.
  • Cleanup — функція очищення обов'язкова для підписок, таймерів і запитів; без неї виникають витоки пам'яті.
  • AbortController — правильний спосіб скасування fetch-запитів всередині useEffect, що запобігає race condition.
  • StrictMode монтує ефект двічі в dev-режимі для перевірки коректності cleanup.
  • Розділяй ефекти — кожен useEffect відповідає за одну задачу, навіть якщо залежності збігаються.
  • eslint-plugin-react-hooks автоматично перевіряє повноту масиву залежностей, скорочуючи кількість багів на 72%.

Ми розробимо мобільний застосунок під ключ

IT Sectr створює застосунки для iOS та Android для стартапів і бізнесу з 2017 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.

Обговорити проект

Читайте також