Как очистить побочный эффект (cleanup function) в useEffect?
Очистка побочного эффекта (cleanup function) в useEffect реализуется с помощью функции, возвращаемой из useEffect. Эта возвращаемая функция вызывается перед тем, как эффект будет повторно выполнен, и перед тем, как компонент будет размонтирован (unmount). Это позволяет корректно очищать ресурсы, удалять слушателей событий, останавливать таймеры, отменять сетевые запросы и так далее.
Синтаксис очистки эффекта
useEffect(() => {
// Побочный эффект
return () => {
// Очистка эффекта
};
}, \[зависимости\]);
Когда вызывается функция очистки
-
Перед повторным вызовом эффекта (если зависимости изменились).
-
При размонтировании компонента.
Пример с setInterval
useEffect(() => {
const intervalId = setInterval(() => {
console.log('tick');
}, 1000);
return () => {
clearInterval(intervalId); // Очистка таймера
};
}, \[\]);
-
Когда компонент монтируется, запускается setInterval.
-
Когда компонент размонтируется, или эффект должен обновиться, вызывается clearInterval.
Пример с подпиской на событие
useEffect(() => {
const onScroll = () => console.log('scrolling');
window.addEventListener('scroll', onScroll);
return () => {
window.removeEventListener('scroll', onScroll);
};
}, \[\]);
Здесь:
-
при маунте — добавляется слушатель события прокрутки;
-
при размонтировании — он удаляется.
Если не удалить подписку, она останется активной и после уничтожения компонента, что приведёт к утечкам памяти и ненужному выполнению кода.
Пример с WebSocket
useEffect(() => {
const socket = new WebSocket('wss://example.com');
socket.onmessage = (event) => {
console.log('Получено сообщение', event.data);
};
return () => {
socket.close(); // Закрываем соединение
};
}, \[\]);
Без socket.close() соединение будет продолжаться даже после размонтирования компонента, что приведёт к утечке ресурсов.
Пример с AbortController для fetch
useEffect(() => {
const controller = new AbortController();
fetch('/api/data', { signal: controller.signal })
.then(res => res.json())
.then(console.log)
.catch(error => {
if (error.name === 'AbortError') {
console.log('Запрос был отменён');
}
});
return () => {
controller.abort(); // Отменяем запрос при очистке
};
}, \[\]);
Если запрос ещё не завершился, и компонент размонтируется, то abort() остановит запрос, предотвращая гонки данных и лишнюю нагрузку.
Пример с зависимостями
useEffect(() => {
const id = setInterval(() => {
console.log('Interval работает с ID =', id);
}, 1000);
return () => {
clearInterval(id);
console.log('Очистка интервала с ID =', id);
};
}, \[someValue\]);
Если someValue меняется, эффект срабатывает повторно, но сначала React вызовет функцию очистки, чтобы удалить предыдущий интервал, и затем создаст новый.
Что произойдет, если не очистить?
-
Таймеры продолжат выполняться даже после уничтожения компонента.
-
События DOM будут продолжать вызываться и приводить к ошибкам или утечкам.
-
WebSocket и другие соединения останутся открытыми.
-
Сетевые запросы могут завершиться после того, как компонент уже исчез — и попытаться обновить состояние несуществующего компонента.
Это приведет к ошибкам в консоли вроде:
Can't perform a React state update on an unmounted component
Поведение React с cleanup-функцией
-
При первом рендере эффект вызывается.
-
Если компонент обновляется:
-
React сначала вызывает функцию очистки, возвращённую в предыдущем useEffect.
-
Затем вызывает новую функцию эффекта.
-
-
Если компонент размонтируется, React вызывает функцию очистки один раз, если она была задана.
Почему важно использовать return в useEffect
-
Это единственный способ управлять побочными эффектами во времени жизни компонента.
-
Без очистки можно получить накопление слушателей, мусор в памяти, утечки, бесконтрольное поведение UI.
Пример без очистки (неправильно)
useEffect(() => {
const id = setInterval(() => {
console.log('tick');
}, 1000);
}, \[\]);
Здесь clearInterval не вызывается — даже если компонент размонтируется, таймер продолжит работу, что приведёт к утечке.
Особенности:
-
Функция очистки вызывается перед выполнением нового эффекта и перед размонтированием.
-
useEffect может возвращать только одну функцию — возвращать промисы или объекты нельзя.
-
Очистка — не обязательно. Но если есть ресурсы, которые нужно освобождать, она строго необходима.
Правильное поведение всегда включает:
-
Подписался → отписался.
-
Запустил таймер → очистил таймер.
-
Начал запрос → отменил запрос.
-
Открыл соединение → закрыл соединение.