Что делает useEffect и какие у него есть зависимости?
Хук useEffect в React позволяет выполнять побочные эффекты в функциональных компонентах. Побочные эффекты — это операции, которые не связаны напрямую с рендерингом интерфейса, но часто необходимы в приложении: получение данных с сервера, изменение DOM вручную, настройка подписок, таймеров, логирование и т.д. До появления хуков такие операции реализовывались в методах жизненного цикла классовых компонентов (componentDidMount, componentDidUpdate, componentWillUnmount). useEffect объединяет их функциональность в едином API.
1. Синтаксис useEffect
useEffect(() => {
// побочный эффект
return () => {
// очистка эффекта (опционально)
};
}, \[dep1, dep2\]);
-
Первый аргумент — функция, внутри которой выполняется эффект.
-
Второй аргумент — массив зависимостей. React будет повторно вызывать эффект только тогда, когда изменится хотя бы одна из зависимостей.
2. Когда вызывается useEffect
Эффект вызывается после того, как компонент отрендерен и отрисован в DOM. Он не блокирует рендеринг и выполняется асинхронно по отношению к нему. Это позволяет избежать задержек в интерфейсе.
3. Типы зависимостей и поведения
a. Без второго аргумента (выполняется после каждого рендера):
useEffect(() => {
console.log('Эффект каждый раз');
});
Вызывается после каждого рендера — как при маунте, так и при обновлениях. Используется редко, так как может приводить к избыточным вызовам.
b. Пустой массив зависимостей (выполняется один раз при маунте):
useEffect(() => {
console.log('Монтирование компонента');
}, \[\]);
Эффект будет выполнен один раз, при монтировании компонента. Это аналог componentDidMount.
c. Список зависимостей (выполняется при изменении зависимостей):
useEffect(() => {
console.log('Обновилось значение name');
}, \[name\]);
Эффект будет срабатывать только если значение name изменилось с предыдущего рендера.
4. Очистка эффекта (return-функция)
Функция, возвращаемая из useEffect, вызывается перед:
-
размонтированием компонента (аналог componentWillUnmount)
-
повторным выполнением эффекта (если изменились зависимости)
Пример с подпиской:
useEffect(() => {
const id = setInterval(() => {
console.log('Таймер');
}, 1000);
return () => {
clearInterval(id);
console.log('Таймер остановлен');
};
}, \[\]);
Очистка необходима для предотвращения утечек памяти и «висячих» подписок.
5. Пример с запросом данных
import { useState, useEffect } from 'react';
function UserProfile({ userId }) {
const \[user, setUser\] = useState(null);
useEffect(() => {
fetch(\`/api/users/${userId}\`)
.then(res => res.json())
.then(data => setUser(data));
}, \[userId\]);
return <div>{user ? user.name : 'Загрузка...'}</div>;
}
-
Эффект срабатывает при первом рендере и каждый раз, когда меняется userId.
-
Если не указать зависимости, fetch будет вызываться при каждом рендере, что неэффективно.
6. Общие случаи использования useEffect
-
Получение данных (fetch, axios)
-
Подписка на WebSocket, события, потоки
-
Манипуляции с DOM (например, установка фокуса)
-
Таймеры и интервалы (setTimeout, setInterval)
-
Интеграция с внешними библиотеками (Google Maps, D3 и т.п.)
-
Сохранение данных в localStorage или сессии
-
Отслеживание изменений props/state
7. Что может быть в зависимостях
Массив зависимостей может содержать:
-
props (например, userId, isOpen)
-
значения из useState
-
функции, полученные извне
-
значения из useCallback, useMemo
Важно: все зависимости должны быть перечислены. React не проверяет их автоматически — если вы не укажете какую-то переменную, это приведёт к багам (например, эффект будет использовать устаревшее значение).
8. Использование с асинхронными функциями
useEffect не принимает async-функцию напрямую, потому что она возвращает промис, а React ожидает функцию очистки или undefined.
Правильный способ:
useEffect(() => {
async function fetchData() {
const res = await fetch('/api/data');
const data = await res.json();
setData(data);
}
fetchData();
}, \[\]);
9. Потенциальные ошибки при использовании useEffect
-
Отсутствие зависимостей — эффект не обновляется при изменении данных.
-
Лишние зависимости — эффект вызывается слишком часто.
-
Неправильная очистка — висячие обработчики событий, утечки памяти.
-
Асинхронные гонки — запросы могут приходить в неправильном порядке.
Для борьбы с гонками можно использовать флаг isMounted или AbortController:
useEffect(() => {
let isCancelled = false;
async function loadUser() {
const res = await fetch('/api/user');
if (!isCancelled) {
setUser(await res.json());
}
}
loadUser();
return () => {
isCancelled = true;
};
}, \[\]);
10. Хуки внутри эффектов
Внутри useEffect нельзя вызывать хуки (useState, useEffect, useContext и т.п.) — это нарушает правила хуков. Все хуки должны быть на верхнем уровне тела компонента.
11. Зависимость от функции
Если в useEffect используется функция, созданная внутри компонента, она будет создаваться заново при каждом рендере. Это приведёт к перезапуску эффекта. Чтобы избежать этого, можно использовать useCallback:
const handleClick = useCallback(() => {
console.log('Клик!');
}, \[\]);
useEffect(() => {
document.addEventListener('click', handleClick);
return () => document.removeEventListener('click', handleClick);
}, \[handleClick\]);
12. Поведение при ререндере
Если зависимость не изменилась (сравнивается поверхностно через Object.is), React не перезапускает эффект. Это даёт возможность контролировать точные условия, при которых эффект будет вызван.
13. useEffect vs useLayoutEffect
-
useEffect выполняется после отрисовки DOM.
-
useLayoutEffect — до отрисовки, синхронно. Это важно при прямой манипуляции DOM, когда нужно измерить элементы до того, как пользователь увидит их.
В 99% случаев используют useEffect, потому что useLayoutEffect может блокировать интерфейс.