← Назад к вопросам
Как происходит отписка от события в 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 запросов