Что делает хук useEffect?

Хук useEffect — один из базовых и наиболее важных хуков в React и React Native, позволяющий управлять побочными эффектами в функциональных компонентах. Побочные эффекты — это любые действия, выходящие за рамки «чистого» рендеринга: сетевые запросы, подписки, таймеры, взаимодействие с API браузера или нативной среды и т. д.

До появления хуков такие действия реализовывались в методах жизненного цикла классовых компонентов (componentDidMount, componentDidUpdate, componentWillUnmount). Хук useEffect позволяет добиться такого же поведения в функциональных компонентах.

Синтаксис

useEffect(() => {
// код с побочным эффектом
return () => {
// функция очистки (опционально)
};
}, \[зависимости\]);
  • Первый аргумент — функция-эффект, которая запускается после рендера.

  • Второй аргумент — массив зависимостей. Он определяет, когда именно должен вызываться эффект.

Что такое побочные эффекты?

Побочные эффекты — это действия, которые:

  • не относятся напрямую к отрисовке UI,

  • могут изменять что-то вне компонента (запросы, таймеры, локальное хранилище, события и т.д.),

  • должны происходить после рендера (например, отправка запроса к серверу).

Пример: вызов API после монтирования

import { useEffect, useState } from 'react';
import { Text } from 'react-native';
function UserProfile({ userId }) {
const \[user, setUser\] = useState(null);
useEffect(() => {
fetch(\`https://api.example.com/users/${userId}\`)
.then(res => res.json())
.then(data => setUser(data));
}, \[userId\]);
return <Text>{user ? user.name : 'Загрузка...'}</Text>;
}
  • Эффект вызывается каждый раз, когда userId меняется.

  • Внутри выполняется HTTP-запрос.

  • Как только ответ получен — обновляется состояние, что вызывает повторный рендер.

Когда срабатывает useEffect

Хук useEffect срабатывает после рендера компонента, но до отображения на экране. Его можно сравнить с componentDidMount и componentDidUpdate.

Второй аргумент — массив зависимостей

Он определяет, когда useEffect должен быть вызван.

Вариант 1: Без массива зависимостей

useEffect(() => {
// вызывается после каждого рендера
});

Этот эффект будет запускаться после каждого обновления компонента, включая начальную отрисовку. Такой подход редко используется, так как может вызывать лишние вызовы.

Вариант 2: С пустым массивом []

useEffect(() => {
// вызывается только один раз при монтировании (аналог componentDidMount)
}, \[\]);

Такой эффект срабатывает только один раз, при первом появлении компонента. Полезно для:

  • получения данных,

  • установки подписок,

  • инициализации.

Вариант 3: С массивом зависимостей [dep1, dep2, ...]

useEffect(() => {

// будет вызываться при изменении любой из зависимостей

}, [count, userId]);

  • Эффект вызывается только если count или userId изменились с предыдущего рендера.

  • Это предотвращает ненужные вызовы эффекта, повышая производительность.

Очистка эффекта

Если ваш эффект устанавливает подписки, таймеры, слушатели событий и т.п., их нужно очищать при размонтировании или при следующем вызове эффекта. Для этого возвращается функция очистки.

useEffect(() => {
const interval = setInterval(() => {
console.log('Тик');
}, 1000);
return () => {
clearInterval(interval);
console.log('Интервал очищен');
};
}, \[\]);
  • return возвращает функцию очистки, которая будет вызвана:

    • перед удалением компонента (аналог componentWillUnmount);

    • перед запуском следующей итерации эффекта (если зависимости изменились).

Пример: подписка и отписка от события

useEffect(() => {
const onResize = () => console.log('resize');
window.addEventListener('resize', onResize);
return () => {
window.removeEventListener('resize', onResize);
};
}, \[\]);

Аналогично в React Native с BackHandler, Dimensions, Keyboard, AppState, и другими слушателями.

Важные замечания

1. Эффекты асинхронны по своей природе, но нельзя напрямую использовать async в useEffect:

useEffect(async () => { ... }); //  не работает

Нужно делать так:

useEffect(() => {
const fetchData = async () => {
const data = await getData();
setData(data);
};
fetchData();
}, \[\]);

2. При использовании эффектов важно указывать все зависимости, от которых зависит эффект. Иначе можно столкнуться с багами, связанными с устаревшими значениями.

useEffect и утечки памяти

Иногда при быстрой смене компонента или состояния может возникнуть ошибка:

Can't perform a React state update on an unmounted component.

Это связано с тем, что эффект отработал, но компонент уже удалён. Чтобы избежать таких ситуаций:

useEffect(() => {
let isMounted = true;
fetchData().then(result => {
if (isMounted) {
setData(result);
}
});
return () => {
isMounted = false;
};
}, \[\]);

useEffect vs другие хуки

Хук Назначение
useState Управление локальным состоянием
--- ---
useEffect Выполнение побочных эффектов после рендера
--- ---
useMemo Кеширование результатов вычислений
--- ---
useCallback Кеширование функции
--- ---
useContext Использование контекста
--- ---

useEffect часто используется в паре с useState для получения и обновления данных.

Часто используемые сценарии с useEffect

Загрузка данных

useEffect(() => {
fetchData().then(setData);
}, \[\]);

Установка и очистка таймеров

useEffect(() => {
const id = setTimeout(() => console.log('через 1с'), 1000);
return () => clearTimeout(id);
}, \[\]);

Подписка на глобальные события

useEffect(() => {
const handleKeyDown = e => {
if (e.key === 'Escape') console.log('Выход');
};
document.addEventListener('keydown', handleKeyDown);
return () => {
document.removeEventListener('keydown', handleKeyDown);
};
}, \[\]);

Реакция на изменение props/state

useEffect(() => {
console.log(\`Пользователь сменился: ${userId}\`);
}, \[userId\]);

useEffect без зависимостей — ловушка

useEffect(() => {
setCount(count + 1);
}); //  бесконечный цикл

Каждый setCount вызывает ререндер → снова setCount → бесконечно. Чтобы этого избежать, всегда указывайте зависимости или оборачивайте обновление в условие.

React Strict Mode и двойной вызов useEffect

В режиме StrictMode React в dev-среде может дважды вызвать эффект при монтировании, чтобы проверить, правильно ли работает очистка. Это не баг, а способ помочь выявить ошибки.

Хук useEffect является фундаментальным для управления жизненным циклом компонентов в функциональном стиле. Он позволяет выполнять действия после рендера, подписываться на события, взаимодействовать с API и очищать ресурсы. Его гибкость делает возможным реализацию практически любого побочного поведения, характерного для современного приложения.