Hack Frontend Community

Как дебажить приложение и находить утечки памяти

Общий процесс дебага

1

Определить проблему

  • Приложение зависает, тормозит или потребляет слишком много памяти?
  • Используйте инструменты браузера: DevTools (Performance, Memory), Console, React Profiler.
2

Анализ логов и ошибок

  • Добавляйте console.log, console.trace, console.error для отслеживания.
  • Используйте внешние инструменты: Sentry, LogRocket, Datadog для логирования ошибок.
3

Профилирование производительности

  • Performance вкладка в Chrome DevTools — анализ FPS, времени загрузки и рендера.
  • React Profiler — выявление лишних ререндеров.
  • Lighthouse — рекомендации по оптимизации.
4

Анализ утечек памяти

  • Используйте вкладку Memory и снимки Heap Snapshot.
  • Ищите объекты, которые не удаляются после размонтирования компонента.
  • Следите за setInterval, setTimeout, WebSocket, EventListeners.

Причины утечек памяти и решения

Забытые таймеры и интервалы

useEffect(() => {
  const interval = setInterval(() => console.log("tick"), 1000);
  return () => clearInterval(interval); // обязательно очищаем!
}, []);

Неудалённые обработчики событий

useEffect(() => {
  const handleScroll = () => console.log("scrolling...");
  window.addEventListener("scroll", handleScroll);
  return () => window.removeEventListener("scroll", handleScroll);
}, []);

Замыкания, удерживающие данные

Замыкания могут "запомнить" ссылки на объекты, и те не будут удалены. Решение: использовать useRef, useCallback или вовремя очищать.

Глобальные переменные

window.myCache = largeObject; // плохая практика

// лучше явно очищать
window.myCache = null;

Ссылки в useRef и DOM

const ref = useRef(null);

useEffect(() => {
  ref.current = document.getElementById("my-element");

  return () => {
    ref.current = null; // освобождаем ссылку
  };
}, []);

WebSocket или подписки, не закрытые вовремя

useEffect(() => {
  const socket = new WebSocket("wss://example.com");
  return () => socket.close(); // обязательно закрываем
}, []);

Отсутствие виртуализации на больших списках

Рендеринг 1000+ элементов без виртуализации замедляет приложение и "забивает" память.

Решение: использовать библиотеки:

Как найти утечки памяти

Вкладка Memory → Heap Snapshot

  • Перейдите в DevTools → Memory → Take Snapshot
  • Взаимодействуйте с компонентом (например, откройте/закройте модалку)
  • Снимите ещё один Snapshot
  • Сравните: должны исчезнуть временные объекты

Вкладка Performance → Record Allocations

  • Нажмите «Start recording allocations»
  • Используйте компонент
  • Нажмите «Stop» и ищите утечки, которые остаются после удаления компонента

Инструменты мониторинга

  • Chrome DevTools — встроенный анализатор памяти и профайлер
  • Sentry, Datadog, LogRocket — обнаружение и логирование утечек и ошибок в проде
  • why-did-you-render — помогает найти лишние ререндеры компонентов

Важно:

Утечки памяти особенно опасны в SPA (Single Page Application), т.к. приложение живёт долго. Даже небольшие утечки могут привести к деградации производительности со временем.

Вывод

  • Следите за очисткой side-effects (setInterval, event listeners, sockets)
  • Используйте DevToolsMemorySnapshot для диагностики
  • Не храните глобальные объекты или замыкания, которые не очищаются
  • Для больших списков — применяйте виртуализацию
  • Используйте React Profiler и профилирование в браузере, чтобы находить узкие места
// Пример шаблона безопасного useEffect
useEffect(() => {
  const res = startSomething();
  return () => {
    cleanupSomething(res);
  };
}, []);