Для чего нужна cleanup у useEffect?
Хуки
Cleanup — функция, возвращаемая из эффекта. Нужна, чтобы отменять/убирать то, что эффект начал: без неё подписки, таймеры и запросы «переживают» компонент и накапливаются.
Когда вызывается
- Перед каждым повторным запуском эффекта (изменились зависимости) — сначала прибраться за старым, затем запустить новый.
- При размонтировании компонента.
Задачи cleanup
Отписки от событий и каналов:
useEffect(() => {
const onResize = () => setWidth(window.innerWidth);
window.addEventListener('resize', onResize);
return () => window.removeEventListener('resize', onResize);
}, []);Без отписки: утечка памяти + обработчики-«зомби», дёргающие setState размонтированного компонента.
Очистка таймеров:
useEffect(() => {
const id = setInterval(poll, 5000);
return () => clearInterval(id);
}, []);Закрытие соединений (WebSocket, EventSource), иначе с каждым перемонтированием их становится больше.
Отмена запросов / защита от гонок:
useEffect(() => {
const ctrl = new AbortController();
fetch(`/api/user/${id}`, { signal: ctrl.signal })
.then((r) => r.json())
.then(setUser)
.catch(() => {});
return () => ctrl.abort(); // старый запрос не перезапишет новый ответ
}, [id]);При быстрой смене id ответы могут прийти в обратном порядке — cleanup отменяет устаревший.
Почему это критично
StrictMode (dev, React 18) специально монтирует компонент дважды: эффект → cleanup → эффект. Если cleanup честный — приложение не заметит; если нет — дубли запросов и подписок проявятся сразу, а не «загадочно в проде».
Коротко для собеседования
Cleanup отменяет начатое эффектом: отписки, clearInterval, закрытие соединений, abort запросов (защита от гонок). Вызывается перед каждым перезапуском эффекта и при размонтировании; его корректность проверяет двойное монтирование StrictMode. Правило: каждый эффект с подпиской/таймером обязан вернуть cleanup.