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

Как реализовать Mounting удаление?

2.0 Middle🔥 161 комментариев
#JavaScript Core

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

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

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

Mounting и Unmounting компонентов

Это критическая концепция в React и других фреймворках. Разберём полный цикл жизни компонента.

Жизненный цикл компонента

Все компоненты проходят три этапа:

1. MOUNTING (монтирование) - компонент добавляется в DOM
2. UPDATING (обновление) - компонент получает новые props/state
3. UNMOUNTING (размонтирование) - компонент удаляется из DOM

Mounting в React

useEffect для инициализации

Когда компонент монтируется, часто нужно выполнить инициализацию:

import { useEffect, useState } from 'react';

function UserProfile({ userId }) {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);

  // Выполняется ТОЛЬКО при монтировании (пустой dependency array)
  useEffect(() => {
    console.log('Компонент смонтирован!');
    
    // Загружаем данные пользователя
    fetchUser(userId).then(data => {
      setUser(data);
      setLoading(false);
    });
  }, []); // пустой array = выполнить один раз

  if (loading) return <div>Загружаю...</div>;
  return <div>{user?.name}</div>;
}

Unmounting в React

Cleanup функция в useEffect

Нужно очистить ресурсы при размонтировании:

import { useEffect } from 'react';

function ChatBox() {
  useEffect(() => {
    console.log('Компонент смонтирован - подключаюсь к WebSocket');
    
    // Подключаемся при монтировании
    const ws = new WebSocket('wss://chat.example.com');
    
    ws.onopen = () => console.log('Подключены');
    ws.onmessage = (event) => console.log('Сообщение:', event.data);

    // Cleanup функция выполняется при размонтировании
    return () => {
      console.log('Компонент размонтирован - отключаюсь от WebSocket');
      ws.close();
    };
  }, []); // выполнить один раз

  return <div>Чат загружается...</div>;
}

Практические примеры

Пример 1: Event listeners

function Window() {
  useEffect(() => {
    const handleResize = () => {
      console.log('Окно изменено:', window.innerWidth);
    };

    // Добавляем слушатель при монтировании
    window.addEventListener('resize', handleResize);

    // Удаляем при размонтировании
    return () => {
      window.removeEventListener('resize', handleResize);
    };
  }, []);

  return <div>Отслеживаю изменение размера</div>;
}

Пример 2: Timer/Interval

function Timer() {
  const [seconds, setSeconds] = useState(0);

  useEffect(() => {
    // Запускаем интервал при монтировании
    const interval = setInterval(() => {
      setSeconds(s => s + 1);
    }, 1000);

    // Очищаем интервал при размонтировании
    return () => {
      clearInterval(interval);
    };
  }, []);

  return <div>Прошло {seconds} секунд</div>;
}

Пример 3: Fetch и отмена запроса

function DataFetcher({ id }) {
  const [data, setData] = useState(null);

  useEffect(() => {
    // Используем AbortController для отмены запроса
    const controller = new AbortController();

    const fetchData = async () => {
      try {
        const response = await fetch(`/api/data/${id}`, {
          signal: controller.signal
        });
        const json = await response.json();
        setData(json);
      } catch (error) {
        if (error.name !== 'AbortError') {
          console.error('Ошибка:', error);
        }
      }
    };

    fetchData();

    // Cleanup: отменяем запрос если компонент размонтирован
    return () => {
      controller.abort();
    };
  }, [id]); // переломаем данные если id изменился

  return <div>{data ? JSON.stringify(data) : 'Загружаю...'}</div>;
}

Пример 4: LocalStorage синхронизация

function ThemeSelector() {
  const [theme, setTheme] = useState('light');

  // Монтирование: загружаем сохранённую тему
  useEffect(() => {
    const saved = localStorage.getItem('theme') || 'light';
    setTheme(saved);
  }, []);

  // Обновление: сохраняем в localStorage
  useEffect(() => {
    localStorage.setItem('theme', theme);
    document.documentElement.className = theme;
  }, [theme]);

  return (
    <button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
      Текущая тема: {theme}
    </button>
  );
}

Dependency Array правила

Это самая частая ошибка новичков:

// ОШИБКА 1: Забыл dependency array - выполняется при каждом рендере!
useEffect(() => {
  fetchData(); // ДОРОГО! Постоянно загружаем
});

// ПРАВИЛЬНО: пустой array - один раз при монтировании
useEffect(() => {
  fetchData();
}, []);

// ПРАВИЛЬНО: зависит от id - при изменении id перезагружаем
useEffect(() => {
  fetchData(id);
}, [id]);

// ОШИБКА 2: Забыл добавить зависимость
useEffect(() => {
  fetchUser(userId); // userId не в dependencies!
}, []); // НЕПРАВИЛЬНО!

// ПРАВИЛЬНО:
useEffect(() => {
  fetchUser(userId);
}, [userId]); // теперь будет перезагружаться при изменении userId

// ОШИБКА 3: Функции и объекты
function Component({ config, onCallback }) {
  useEffect(() => {
    // config и onCallback создаются заново при каждом рендере!
    // это может вызвать бесконечный цикл
  }, [config, onCallback]); // ОПАСНО!
}

// ПРАВИЛЬНО: мемоизируй зависимости
function Component({ config: rawConfig, onCallback }) {
  const config = useMemo(() => rawConfig, [rawConfig]);
  const callback = useCallback(onCallback, [onCallback]);
  
  useEffect(() => {
    // Теперь безопасно
  }, [config, callback]);
}

Vue 3 Lifecycle Hooks

В Vue используются другие хуки:

import { onMounted, onUnmounted } from 'vue';

export default {
  setup() {
    // MOUNTING - компонент добавлен в DOM
    onMounted(() => {
      console.log('Компонент смонтирован');
      // Инициализируем данные, слушателей и т.д.
    });

    // UNMOUNTING - компонент удален из DOM
    onUnmounted(() => {
      console.log('Компонент размонтирован');
      // Очищаем слушатели, таймеры и т.д.
    });

    return {};
  }
};

Чеклист для правильного unmounting

При размонтировании нужно очистить:

// ✓ Event listeners
window.removeEventListener('resize', handler);
dom.removeEventListener('click', handler);

// ✓ Timers
clearInterval(intervalId);
clearTimeout(timeoutId);

// ✓ Subscriptions
unsubscribe();
subscription.unsubscribe();

// ✓ WebSocket/Socket.io
ws.close();
socket.disconnect();

// ✓ HTTP запросы (AbortController)
controller.abort();

// ✓ Observers
intersectionObserver.disconnect();
resizeObserver.disconnect();

// ✓ Всё что может привести к утечке памяти
delete ref.listener;
cache.clear();

Полный пример: Chat компонент

function Chat({ roomId, username }) {
  const [messages, setMessages] = useState([]);
  const [socket, setSocket] = useState(null);

  // Монтирование: подключаемся к комнате
  useEffect(() => {
    console.log(`Монтирование: подключаюсь к комнате ${roomId}`);
    
    const ws = new WebSocket(`wss://chat.example.com/${roomId}`);
    
    ws.onopen = () => {
      console.log('Подключен к сокету');
      ws.send(JSON.stringify({ type: 'join', username }));
    };
    
    ws.onmessage = (event) => {
      const message = JSON.parse(event.data);
      setMessages(prev => [...prev, message]);
    };
    
    ws.onerror = (error) => {
      console.error('Ошибка сокета:', error);
    };
    
    setSocket(ws);

    // Размонтирование: отключаемся от комнаты
    return () => {
      console.log('Размонтирование: отключаюсь от комнаты');
      ws.send(JSON.stringify({ type: 'leave', username }));
      ws.close();
    };
  }, [roomId, username]); // переподключаемся если roomId или username изменились

  const sendMessage = (text) => {
    if (socket && socket.readyState === WebSocket.OPEN) {
      socket.send(JSON.stringify({
        type: 'message',
        text,
        username
      }));
    }
  };

  return (
    <div className="chat">
      <div className="messages">
        {messages.map((msg, i) => (
          <div key={i} className="message">
            <strong>{msg.username}:</strong> {msg.text}
          </div>
        ))}
      </div>
      <ChatInput onSend={sendMessage} />
    </div>
  );
}

Debugging Mounting/Unmounting

// Добавь логирование для отладки
function Component() {
  useEffect(() => {
    console.log('%c Mounting', 'color: green; font-weight: bold');
    
    return () => {
      console.log('%c Unmounting', 'color: red; font-weight: bold');
    };
  }, []);
  
  return <div>Смотри в консоль при монтировании/размонтировании</div>;
}

Итого

Monting и Unmounting — критический аспект управления компонентами:

  1. Mounting - инициализируй данные, слушатели, подписки
  2. Unmounting - очищай всё что инициализировал
  3. Используй cleanup функции в useEffect
  4. Правильно настраивай dependency arrays
  5. Проверяй утечки памяти в DevTools

Неправильное управление жизненным циклом приводит к утечкам памяти, утечкам сокетов и неожиданному поведению.