← Назад к вопросам
Как очистить подписку при размонтировании компонента?
2.0 Middle🔥 202 комментариев
#JavaScript Core
Комментарии (2)
🐱
claude-haiku-4.5PrepBro AI3 апр. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Очистка подписки при размонтировании компонента
Это критически важная практика для предотвращения утечек памяти в React приложениях. Нужно правильно управлять жизненным циклом подписок и ресурсов.
1. Подписки на Event Listeners
Добавляй listener в useEffect и удаляй в cleanup функции:
import { useEffect } from 'react';
function WindowResizeComponent() {
useEffect(() => {
// Добавляем listener при монтировании
const handleResize = () => {
console.log('Window resized:', window.innerWidth);
};
window.addEventListener('resize', handleResize);
// Cleanup функция - удаляет listener при размонтировании
return () => {
window.removeEventListener('resize', handleResize);
};
}, []); // пустые зависимости = эффект запускается один раз при монтировании
return <div>Resize the window</div>;
}
2. Подписки на Observable (RxJS)
Унподписывайся от Observable:
import { useEffect } from 'react';
function ObservableComponent({ dataStream$ }) {
useEffect(() => {
// Подписываемся на Observable
const subscription = dataStream$.subscribe(
(data) => {
console.log('Data received:', data);
},
(error) => {
console.error('Error:', error);
}
);
// Cleanup - отписываемся
return () => {
subscription.unsubscribe();
};
}, [dataStream$]);
return <div>Streaming data</div>;
}
3. Подписки на WebSocket
import { useEffect } from 'react';
function WebSocketComponent() {
useEffect(() => {
const ws = new WebSocket('wss://echo.websocket.org/');
ws.onopen = () => {
console.log('Connected');
ws.send('Hello');
};
ws.onmessage = (event) => {
console.log('Message:', event.data);
};
// Cleanup - закрываем соединение
return () => {
ws.close();
};
}, []);
return <div>WebSocket</div>;
}
4. Таймеры (setTimeout, setInterval)
Очищай таймеры в cleanup функции:
import { useEffect, useState } from 'react';
function TimerComponent() {
const [count, setCount] = useState(0);
useEffect(() => {
// setTimeout
const timeoutId = setTimeout(() => {
console.log('Timeout fired after 5 seconds');
}, 5000);
// setInterval
const intervalId = setInterval(() => {
setCount(c => c + 1);
}, 1000);
// Cleanup
return () => {
clearTimeout(timeoutId);
clearInterval(intervalId);
};
}, []);
return <div>Count: {count}</div>;
}
5. Абортирование fetch запросов
import { useEffect, useState } from 'react';
function FetchComponent() {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
// Создаём AbortController для отмены запроса
const abortController = new AbortController();
const fetchData = async () => {
try {
const response = await fetch('/api/data', {
signal: abortController.signal // передаём сигнал
});
const json = await response.json();
setData(json);
} catch (error) {
if (error.name !== 'AbortError') {
console.error('Fetch error:', error);
}
} finally {
setLoading(false);
}
};
fetchData();
// Cleanup - отменяем запрос
return () => {
abortController.abort();
};
}, []);
return <div>{loading ? 'Loading...' : data}</div>;
}
6. Подписки на Redux store
import { useEffect } from 'react';
import { useSelector } from 'react-redux';
function StoreSubscriptionComponent() {
const data = useSelector(state => state.data);
useEffect(() => {
// В Redux Hook этого делать не нужно - useSelector уже управляет этим
// Но если используешь store.subscribe() напрямую:
const unsubscribe = store.subscribe(() => {
console.log('Store updated');
});
// Cleanup
return () => {
unsubscribe();
};
}, []);
return <div>{data}</div>;
}
7. Подписки на Context
import { useContext, useEffect } from 'react';
const MyContext = React.createContext();
function ContextSubscriber() {
const { subscribe } = useContext(MyContext);
useEffect(() => {
// Подписываемся
const unsubscribe = subscribe((value) => {
console.log('Context value:', value);
});
// Cleanup
return () => {
unsubscribe();
};
}, [subscribe]);
return <div>Subscribed</div>;
}
8. Несколько очисток в одном эффекте
import { useEffect } from 'react';
function ComplexComponent() {
useEffect(() => {
// Listener
const handleClick = () => {};
document.addEventListener('click', handleClick);
// Timer
const timerId = setInterval(() => {}, 1000);
// WebSocket
const ws = new WebSocket('ws://...');
// Cleanup - удаляй всё в обратном порядке
return () => {
document.removeEventListener('click', handleClick);
clearInterval(timerId);
ws.close();
};
}, []);
return <div>Complex component</div>;
}
9. Custom Hook для управления подписками
import { useEffect } from 'react';
function useSubscription(source) {
useEffect(() => {
if (!source) return;
const handleUpdate = (value) => {
console.log('Updated:', value);
};
source.subscribe(handleUpdate);
return () => {
source.unsubscribe(handleUpdate);
};
}, [source]);
}
// Использование
function MyComponent({ dataSource }) {
useSubscription(dataSource);
return <div>Subscribed</div>;
}
Частые ошибки
// НЕПРАВИЛЬНО - утечка памяти
function BadComponent() {
useEffect(() => {
window.addEventListener('resize', () => {
console.log('Resized');
});
// Нет cleanup функции!
}, []);
}
// ПРАВИЛЬНО
function GoodComponent() {
useEffect(() => {
const handler = () => console.log('Resized');
window.addEventListener('resize', handler);
return () => window.removeEventListener('resize', handler);
}, []);
}
Правила при работе с подписками
- Всегда удаляй listener'ы и подписки в cleanup функции
- Используй AbortController для fetch запросов
- Очищай таймеры (clearTimeout, clearInterval)
- Закрывай WebSocket соединения
- Отписывайся от Observable
- Проверяй mounted флаг для асинхронного кода
- Порядок cleanup должен быть обратным порядку подписки