← Назад к вопросам

Как происходит отписка от события в React?

2.2 Middle🔥 221 комментариев
#React#Архитектура и паттерны

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI3 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Как происходит отписка от события в React

Отписка (cleanup) — это механизм удаления подписок на события, которые были созданы в компоненте. Это критично для предотвращения утечек памяти и предсказуемого поведения приложения.

useEffect cleanup функция

В React функциональных компонентах отписка происходит через return функцию в useEffect:

import { useEffect, useState } from 'react';

function ChatComponent() {
  const [messages, setMessages] = useState([]);
  
  useEffect(() => {
    // Подписываемся на события
    const handleMessage = (event) => {
      setMessages(prev => [...prev, event.data]);
    };
    
    window.addEventListener('message', handleMessage);
    
    // Cleanup функция — вызывается при размонтировании компонента
    // или перед повторным вызовом useEffect
    return () => {
      window.removeEventListener('message', handleMessage);
    };
  }, []); // Зависимости: пустой массив = запускается один раз
  
  return <div>{messages.join(', ')}</div>;
}

Когда компонент размонтируется, React автоматически вызовет функцию очистки и удалит event listener.

Различные сценарии отписки

Отписка от обычного события

function SearchInput() {
  const [value, setValue] = useState('');
  
  useEffect(() => {
    const input = document.querySelector('input');
    
    const handleChange = (e) => {
      setValue(e.target.value);
    };
    
    input?.addEventListener('input', handleChange);
    
    return () => {
      input?.removeEventListener('input', handleChange);
    };
  }, []);
  
  return <input type="text" value={value} />;
}

Отписка от таймера

function Timer() {
  const [count, setCount] = useState(0);
  
  useEffect(() => {
    // Создаём таймер
    const interval = setInterval(() => {
      setCount(prev => prev + 1);
    }, 1000);
    
    // Очистка: отменяем таймер
    return () => {
      clearInterval(interval);
    };
  }, []);
  
  return <div>Секунды: {count}</div>;
}

Отписка от WebSocket

function ChatSocket() {
  const [messages, setMessages] = useState([]);
  
  useEffect(() => {
    // Подключаемся к WebSocket
    const socket = new WebSocket('wss://echo.websocket.org');
    
    socket.onmessage = (event) => {
      setMessages(prev => [...prev, event.data]);
    };
    
    // Очистка: закрываем соединение
    return () => {
      socket.close();
    };
  }, []);
  
  return <div>{messages.map((msg, i) => <p key={i}>{msg}</p>)}</div>;
}

Отписка от асинхронной операции

function UserData() {
  const [user, setUser] = useState(null);
  
  useEffect(() => {
    let isMounted = true; // Флаг для проверки монтирования
    
    const fetchUser = async () => {
      const response = await fetch('/api/user');
      const data = await response.json();
      
      // Обновляем состояние только если компонент всё ещё монтирован
      if (isMounted) {
        setUser(data);
      }
    };
    
    fetchUser();
    
    // Очистка
    return () => {
      isMounted = false; // Помечаем компонент как размонтированный
    };
  }, []);
  
  return <div>{user?.name || 'Загрузка...'}</div>;
}

Отписка с зависимостями

function DynamicSubscription({ userId }) {
  const [userData, setUserData] = useState(null);
  
  useEffect(() => {
    // Подписываемся на события для конкретного пользователя
    const handleUserUpdate = (data) => {
      setUserData(data);
    };
    
    // Сложный сценарий: подписка и отписка
    const subscribe = async () => {
      const response = await fetch(`/api/users/${userId}/subscribe`);
      const subscriptionId = await response.json();
      
      // Слушаем события
      window.addEventListener(`user-${userId}`, handleUserUpdate);
      
      return subscriptionId;
    };
    
    let subscriptionId;
    subscribe().then(id => { subscriptionId = id; });
    
    // Очистка при изменении userId или размонтировании
    return () => {
      window.removeEventListener(`user-${userId}`, handleUserUpdate);
      
      // Отписываемся от сервера
      if (subscriptionId) {
        fetch(`/api/unsubscribe`, {
          method: 'POST',
          body: JSON.stringify({ subscriptionId })
        });
      }
    };
  }, [userId]); // Перезапускаем при изменении userId
  
  return <div>{userData?.status || 'Ожидание...'}</div>;
}

Отписка в class компонентах

В старом подходе отписка происходит в componentWillUnmount:

class ChatComponent extends React.Component {
  componentDidMount() {
    this.handleMessage = (event) => {
      this.setState({ messages: [...this.state.messages, event.data] });
    };
    
    window.addEventListener('message', this.handleMessage);
  }
  
  componentWillUnmount() {
    // Здесь происходит очистка
    window.removeEventListener('message', this.handleMessage);
  }
  
  render() {
    return <div>{this.state.messages.join(', ')}</div>;
  }
}

Важные моменты

AbortController для fetch операций:

function FetchData() {
  const [data, setData] = useState(null);
  
  useEffect(() => {
    const controller = new AbortController();
    
    fetch('/api/data', { signal: controller.signal })
      .then(r => r.json())
      .then(d => setData(d))
      .catch(e => {
        if (e.name !== 'AbortError') {
          console.error(e);
        }
      });
    
    return () => {
      controller.abort(); // Отменяем fetch при размонтировании
    };
  }, []);
  
  return <div>{data ? JSON.stringify(data) : 'Загрузка...'}</div>;
}

Рекомендации

  • Всегда создавайте cleanup функцию для event listeners, таймеров и подписок
  • Используйте зависимости правильно чтобы перезапускать эффект при необходимости
  • Используйте isMounted флаг для асинхронных операций чтобы избежать утечек памяти
  • Используйте AbortController для современных fetch запросов