Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI3 апр. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Как избежать утечек памяти при размонтировании компонента
Утечка памяти происходит, когда компонент размонтирован, но на него остаются активные ссылки, таймеры, слушатели событий или асинхронные операции. Это приводит к ненужному использованию памяти и замедлению приложения.
Основная проблема: незавершённые операции
// ❌ Утечка памяти: setInterval никогда не очищается
function Timer() {
useEffect(() => {
setInterval(() => {
console.log('Tick');
}, 1000);
}, []);
return <div>Timer</div>;
}
// Каждый раз, когда компонент монтируется и размонтируется,
// создаётся новый setInterval, который НИКОГДА не останавливается
Решение 1: Очистка через return в useEffect
Вернёшь функцию cleanup из useEffect, которая выполняется при размонтировании:
function Timer() {
useEffect(() => {
const intervalId = setInterval(() => {
console.log('Tick');
}, 1000);
// ✅ Cleanup: очищает интервал при размонтировании
return () => clearInterval(intervalId);
}, []);
return <div>Timer</div>;
}
Как это работает:
- При монтировании: запускается setInterval
- При размонтировании: выполняется cleanup функция и clearInterval
- Интервал полностью удаляется из памяти
Решение 2: Очистка слушателей событий
// ❌ Утечка: addEventListener без removeEventListener
function Window() {
useEffect(() => {
window.addEventListener('resize', handleResize);
}, []);
return <div>Responsive</div>;
}
// ✅ Правильно: cleanup удаляет слушатель
function Window() {
useEffect(() => {
const handleResize = () => {
console.log('Resized');
};
window.addEventListener('resize', handleResize);
// Cleanup
return () => window.removeEventListener('resize', handleResize);
}, []);
return <div>Responsive</div>;
}
Решение 3: Очистка асинхронных операций (fetch, axios)
// ❌ Утечка: fetch завершится даже после размонтирования
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
useEffect(() => {
fetch(`/api/users/${userId}`)
.then(r => r.json())
.then(data => setUser(data)); // Ошибка: компонент может быть размонтирован!
}, [userId]);
return <div>{user?.name}</div>;
}
// ✅ Правильно: используй AbortController
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
useEffect(() => {
const controller = new AbortController();
fetch(`/api/users/${userId}`, { signal: controller.signal })
.then(r => r.json())
.then(data => {
// Проверяем, что компонент ещё смонтирован
if (!controller.signal.aborted) {
setUser(data);
}
})
.catch(error => {
// AbortError = компонент был размонтирован
if (error.name !== 'AbortError') {
console.error(error);
}
});
// Cleanup: отменяет запрос при размонтировании
return () => controller.abort();
}, [userId]);
return <div>{user?.name}</div>;
}
Решение 4: Ref флаг для отслеживания монтирования
function DataFetcher() {
const [data, setData] = useState(null);
const isMountedRef = useRef(true);
useEffect(() => {
isMountedRef.current = true;
const fetchData = async () => {
const result = await api.get('/data');
// ✅ Проверка: установим state только если компонент всё ещё смонтирован
if (isMountedRef.current) {
setData(result);
}
};
fetchData();
// Cleanup: отметим, что компонент размонтирован
return () => {
isMountedRef.current = false;
};
}, []);
return <div>{data?.name}</div>;
}
Решение 5: Очистка таймеров (setTimeout, setInterval)
// ❌ Утечка
function Notification() {
useEffect(() => {
setTimeout(() => {
console.log('Notification');
}, 3000);
}, []);
return <div>Notification</div>;
}
// ✅ Правильно
function Notification() {
useEffect(() => {
const timeoutId = setTimeout(() => {
console.log('Notification');
}, 3000);
return () => clearTimeout(timeoutId);
}, []);
return <div>Notification</div>;
}
Решение 6: Очистка подписок (Observable, WebSocket)
// ❌ Утечка: подписка никогда не отменяется
function DataStream() {
const [data, setData] = useState(null);
useEffect(() => {
const subscription = dataStream$.subscribe(value => {
setData(value);
});
}, []);
return <div>{data}</div>;
}
// ✅ Правильно: отменяем подписку
function DataStream() {
const [data, setData] = useState(null);
useEffect(() => {
const subscription = dataStream$.subscribe(value => {
setData(value);
});
return () => subscription.unsubscribe();
}, []);
return <div>{data}</div>;
}
Решение 7: Очистка WebSocket соединений
function LiveChat() {
const [messages, setMessages] = useState([]);
useEffect(() => {
const ws = new WebSocket('wss://api.example.com/chat');
ws.onmessage = (event) => {
setMessages(prev => [...prev, event.data]);
};
// ✅ Cleanup: закрываем соединение
return () => {
ws.close();
};
}, []);
return <div>{messages.join(', ')}</div>;
}
Чеклист для избежания утечек
В каждом useEffect с побочными эффектами:
- Таймеры (setTimeout, setInterval) — clearTimeout/clearInterval
- Слушатели событий (addEventListener) — removeEventListener
- Асинхронные запросы (fetch) — AbortController или флаг isMounted
- Подписки (RxJS) — unsubscribe()
- WebSocket, WebRTC — close()
- DOM манипуляции — очистить ссылки на DOM элементы
Пример с несколькими операциями
function ComplexComponent() {
const [data, setData] = useState(null);
const isMountedRef = useRef(true);
useEffect(() => {
isMountedRef.current = true;
// 1. Fetch
const controller = new AbortController();
fetch('/api/data', { signal: controller.signal })
.then(r => r.json())
.then(d => {
if (isMountedRef.current) setData(d);
});
// 2. Интервал
const intervalId = setInterval(() => {
console.log('Poll');
}, 5000);
// 3. Слушатель
const handleResize = () => console.log('Resize');
window.addEventListener('resize', handleResize);
// ✅ Cleanup всего
return () => {
isMountedRef.current = false;
controller.abort();
clearInterval(intervalId);
window.removeEventListener('resize', handleResize);
};
}, []);
return <div>{data?.name}</div>;
}
Резюме
Утечки памяти при размонтировании происходят из-за незавершённых операций. Всегда используй cleanup функции в useEffect:
- Таймеры → clearTimeout/clearInterval
- События → removeEventListener
- Запросы → AbortController
- Подписки → unsubscribe()
- Соединения → close()
Это критично для создания эффективного и стабильного приложения.