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
// Dependencies control when effect re-runs
useEffect(() => {
    document.title = `User: ${user.name}`;
}, [user.name]); // re-run only when user.name changes

// eslint-disable-next-line react-hooks/exhaustive-deps
// If you omit a dependency, you get stale data

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 или bComponentWillReceiveProps-аналог
cleanup returnУправление размонтированием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);

    // Cleanup function — runs on unmount and before re-run
    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 — no cancellation
useEffect(() => {
    fetch(`/api/user/${userId}`).then(res => setUser(res));
}, [userId]);

// ✅ Fixed with 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 года. Мы проконсультируем вас и предложим наилучшее решение.

Обсудить проект

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