PassFront
  1. PassFront
  2. База знаний
  3. Хуки

Как в useEffect в React снять обработчик при размонтировании компонента?

Хуки

Обработчик снимается в cleanup-функции эффекта: то, что эффект повесил, возвращаемая функция снимает — React вызовет её при размонтировании.

Базовый паттерн

useEffect(() => {
  const handleScroll = () => setOffset(window.scrollY);

  window.addEventListener('scroll', handleScroll);

  return () => {
    window.removeEventListener('scroll', handleScroll); // при размонтировании
  };
}, []);

Критично: removeEventListener должен получить ту же самую ссылку на функцию, что и addEventListener, — поэтому обработчик объявляют внутри эффекта (общее замыкание), а не инлайном в обоих вызовах.

Нюансы

  • С пустым массивом deps cleanup выполнится только при размонтировании; с [deps] — ещё и перед каждым перезапуском эффекта (старый обработчик снимется, новый повесится — это правильно).
  • StrictMode (dev) монтирует компонент дважды: подписка → отписка → подписка. Если отписка написана честно, дублей не будет — так React её и проверяет.
  • Обработчик, использующий state, лучше держать «стабильным»: функциональные сеттеры (setX((x) => ...)) избавляют от пересоздания подписки на каждое изменение.

Современная альтернатива: AbortController

Один signal снимает пачку слушателей:

useEffect(() => {
  const ctrl = new AbortController();
  window.addEventListener('scroll', onScroll, { signal: ctrl.signal });
  window.addEventListener('resize', onResize, { signal: ctrl.signal });
  return () => ctrl.abort(); // снимает оба
}, []);

Коротко для собеседования

Повесить слушатель в эффекте, вернуть cleanup с removeEventListener той же функции (объявленной внутри эффекта); с [] cleanup сработает при размонтировании, с deps — и перед перезапусками. Проверяется StrictMode-двойным монтированием; для нескольких слушателей удобен AbortController с signal.

Другие вопросы раздела