Как очистить побочный эффект (cleanup function) в useEffect?

Очистка побочного эффекта (cleanup function) в useEffect реализуется с помощью функции, возвращаемой из useEffect. Эта возвращаемая функция вызывается перед тем, как эффект будет повторно выполнен, и перед тем, как компонент будет размонтирован (unmount). Это позволяет корректно очищать ресурсы, удалять слушателей событий, останавливать таймеры, отменять сетевые запросы и так далее.

Синтаксис очистки эффекта

useEffect(() => {
// Побочный эффект
return () => {
// Очистка эффекта
};
}, \[зависимости\]);

Когда вызывается функция очистки

  1. Перед повторным вызовом эффекта (если зависимости изменились).

  2. При размонтировании компонента.

Пример с 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-функцией

  1. При первом рендере эффект вызывается.

  2. Если компонент обновляется:

    • React сначала вызывает функцию очистки, возвращённую в предыдущем useEffect.

    • Затем вызывает новую функцию эффекта.

  3. Если компонент размонтируется, React вызывает функцию очистки один раз, если она была задана.

Почему важно использовать return в useEffect

  • Это единственный способ управлять побочными эффектами во времени жизни компонента.

  • Без очистки можно получить накопление слушателей, мусор в памяти, утечки, бесконтрольное поведение UI.

Пример без очистки (неправильно)

useEffect(() => {
const id = setInterval(() => {
console.log('tick');
}, 1000);
}, \[\]);

Здесь clearInterval не вызывается — даже если компонент размонтируется, таймер продолжит работу, что приведёт к утечке.

Особенности:

  • Функция очистки вызывается перед выполнением нового эффекта и перед размонтированием.

  • useEffect может возвращать только одну функцию — возвращать промисы или объекты нельзя.

  • Очистка — не обязательно. Но если есть ресурсы, которые нужно освобождать, она строго необходима.

Правильное поведение всегда включает:

  • Подписался → отписался.

  • Запустил таймер → очистил таймер.

  • Начал запрос → отменил запрос.

  • Открыл соединение → закрыл соединение.