Как очистить подписку при размонтировании компонента?
Жизненный цикл
Подписка снимается в cleanup-функции того же эффекта, который её создал. React вызовет cleanup при размонтировании (и перед каждым перезапуском эффекта).
Шаблон
useEffect(() => {
const subscription = store.subscribe(handleChange);
return () => subscription.unsubscribe(); // отписка
}, [store]);Подписка и отписка живут рядом, поэтому их невозможно рассинхронизировать (в классах это были два разных метода).
Разные виды подписок
// DOM-события
window.addEventListener('resize', onResize);
return () => window.removeEventListener('resize', onResize); // та же ссылка!
// WebSocket
const ws = new WebSocket(url);
return () => ws.close();
// Наблюдатели
const observer = new IntersectionObserver(cb);
observer.observe(node);
return () => observer.disconnect();
// Таймеры
const id = setInterval(tick, 1000);
return () => clearInterval(id);Для removeEventListener обязательно передать ту же функцию: объявлять обработчик внутри эффекта. Несколько слушателей удобно снимать одним AbortController + { signal }.
Нюансы
- С
[]cleanup сработает только при размонтировании; с[deps]— ещё и перед пересозданием подписки (это правильное поведение: отписались от старого источника, подписались на новый). - StrictMode (dev) проверяет корректность: подписка → отписка → подписка при монтировании.
- Забытая отписка = утечка памяти и «зомби»-обработчики, дёргающие setState удалённого компонента.
Коротко для собеседования
Возвращать из useEffect функцию, снимающую подписку: unsubscribe/removeEventListener (с той же ссылкой на обработчик)/close/disconnect/clearInterval. React вызовет её при размонтировании и перед каждым перезапуском эффекта; корректность проверяет двойное монтирование StrictMode.