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

Жизненный цикл компонента — последовательность фаз от создания (mounting) до удаления (unmounting), в которые React выполняет рендеринг, обновление и очистку.

Зачем нужно

Понимание жизненного цикла объясняет когда использовать useEffect и с какими зависимостями, почему нужна функция очистки (cleanup) и как избежать утечек памяти. Без этого знания разработчик пишет эффекты вслепую и получает баги: лишние запросы к API, незакрытые подписки, обновление размонтированного компонента.

Где используется

  • Подписки на WebSocket, EventSource при монтировании → отписка при размонтировании
  • Загрузка данных при монтировании и при изменении пропсов
  • Интеграция со сторонними библиотеками (карты, графики)
  • Таймеры: setInterval при монтировании → clearInterval при размонтировании

Три фазы жизненного цикла

Mounting (монтирование)
    → Компонент создаётся и вставляется в DOM
    → useEffect с  — аналог componentDidMount

Updating (обновление)
    → Props или state изменились → ре-рендер
    → useEffect с зависимостями — аналог componentDidUpdate

Unmounting (размонтирование)
    → Компонент удаляется из DOM
    → Cleanup функция в useEffect — аналог componentWillUnmount

useEffect — хук жизненного цикла

import { useState, useEffect } from 'react';

function UserProfile({ userId }) {
  const [user, setUser] = useState(null);
  const [status, setStatus] = useState('online');

  // 1. Эффект при монтировании + при изменении userId
  useEffect(() => {
    let cancelled = false; // флаг для предотвращения race condition

    setUser(null); // сброс перед новым запросом

    fetch(`/api/users/${userId}`)
      .then(r => r.json())
      .then(data => {
        // Не обновляем state если компонент уже размонтирован
        if (!cancelled) setUser(data);
      });

    // Cleanup: сигнализируем об отмене при смене userId или размонтировании
    return  => { cancelled = true; };
  }, [userId]); // [userId] = запускать при изменении userId

  // 2. Эффект с WebSocket — подписка/отписка
  useEffect(() => {
    const ws = new WebSocket(`wss://api.example.com/status/${userId}`);

    ws.onmessage = (event) => {
      setStatus(event.data);
    };

    // Cleanup: закрываем соединение при размонтировании
    return  => ws.close();
  }, [userId]);

  // 3. Эффект без зависимостей — один раз при монтировании
  useEffect(() => {
    document.title = 'Профиль пользователя';

    return  => {
      document.title = 'Приложение'; // сброс при размонтировании
    };
  }, ); //  = только при монтировании/размонтировании

  if (!user) return <p>Загрузка...</p>;
  return (
    <div>
      <h1>{user.name}</h1>
      <span>Статус: {status}</span>
    </div>
  );
}

Порядок выполнения в React

1. render         ← React вычисляет виртуальный DOM
2. DOM update       ← React обновляет реальный DOM
3. useLayoutEffect  ← синхронно после обновления DOM (редко)
4. useEffect        ← асинхронно после отрисовки браузером

При обновлении:
  old cleanup     ← сначала cleanup предыдущего эффекта
  render
  DOM update
  new effect      ← затем новый эффект

Частые ошибки

  • Нет зависимостей в useEffect — без массива зависимостей эффект запускается после каждого рендера; добавляйте зависимости явно.
  • Нет cleanup функции — подписки, таймеры, WebSocket-соединения не закрываются → утечки памяти и ошибки после размонтирования.
  • Обновление state размонтированного компонента — при асинхронных запросах нужен флаг отмены или AbortController.
  • Бесконечный цикл — если внутри useEffect изменяется state, который включён в зависимости → цикл.

Связанные темы

Ресурсы