Что делает хук 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 и очищать ресурсы. Его гибкость делает возможным реализацию практически любого побочного поведения, характерного для современного приложения.