Хотите заказать веб-сайт? Связаться с нами

Жизненный цикл React: mount, update, unmount

Цель урока: понять, что происходит с React-компонентом на каждом этапе его существования: от создания до удаления. Научиться выполнять код в нужные моменты — при загрузке данных, при обновлении или перед тем, как компонент исчезает с экрана.

Жизненный цикл React: mount, update, unmount

Введение: что такое жизненный цикл

Каждый компонент в React проходит через три основные фазы:

Фаза Что происходит Что можно делать
Монтирование (Mounting) Компонент появляется на экране Загрузить данные, подписаться на события
Обновление (Updating) Компонент перерисовывается (изменился state или props) Реагировать на изменения, обновлять DOM
Размонтирование (Unmounting) Компонент исчезает с экрана Отписаться от событий, очистить таймеры

Главная мысль: у каждого компонента есть «жизненный цикл» — моменты, когда он рождается, обновляется и умирает. На этих этапах можно выполнять нужные действия.

Жизненный цикл в функциональных компонентах (хуки)

В современных React-приложениях (с хуками) жизненным циклом управляет хук useEffect.

Важно: в функциональных компонентах нет отдельных методов, как в классовых. Всё делается через useEffect с разными зависимостями.

Сопоставление классовых методов и хуков

Классовый компонент Функциональный компонент (хуки)
componentDidMount useEffect(() => {...}, [])
componentDidUpdate useEffect(() => {...}, [deps])
componentWillUnmount useEffect(() => { return () => {...} }, [])

Хук useEffect — главный инструмент

Синтаксис:

useEffect(() => {
  // Код, который выполняется при монтировании и обновлении

  return () => {
    // Очистка (выполняется при размонтировании)
  };
}, [зависимости]);

Три варианта использования:

Вариант Код Когда выполняется
Без зависимостей useEffect(() => {...}) При каждом рендере
Пустой массив useEffect(() => {...}, []) Один раз при монтировании
С зависимостями useEffect(() => {...}, [count]) При монтировании и при изменении count

Фаза 1. Монтирование (Mounting)

Что происходит:

  1. React создаёт DOM-элементы
  2. Вставляет их на страницу
  3. Выполняется код после рендера

Как это выглядит в коде:

import { useEffect } from 'react';

function UserProfile({ userId }) {
  useEffect(() => {
    // Код выполняется один раз после первого рендера
    console.log('Компонент смонтирован на страницу');
    
    // Загружаем данные пользователя
    fetchUserData(userId);
    
    // Подписываемся на события
    const handleResize = () => console.log('Окно изменено');
    window.addEventListener('resize', handleResize);
    
    // Возвращаем функцию очистки (будет вызвана при размонтировании)
    return () => {
      console.log('Компонент будет удалён');
      window.removeEventListener('resize', handleResize);
    };
  }, []); // Пустой массив = только при монтировании

  return <div>Профиль пользователя</div>;
}

Типичные задачи при монтировании:

  1. Загрузка данных с сервера (API-запросы)
  2. Подписка на события (resize, scroll, WebSocket)
  3. Настройка таймеров (setInterval, setTimeout)
  4. Работа с DOM (фокус, размеры)

Фаза 2. Обновление (Updating)

Что происходит:

  1. Меняется state или props
  2. React перерисовывает компонент
  3. Выполняется эффект (если зависимости изменились)

Как это выглядит в коде:

function Counter() {
  const [count, setCount] = useState(0);
  const [doubledCount, setDoubledCount] = useState(0);

  useEffect(() => {
    // Код выполняется при каждом изменении count
    console.log(`Счёт изменился: ${count}`);
    setDoubledCount(count * 2);
  }, [count]); // Выполняется только когда count меняется

  return (
    <div>
      <p>Счёт: {count}</p>
      <p>Удвоенный счёт: {doubledCount}</p>
      <button onClick={() => setCount(count + 1)}>+1</button>
    </div>
  );
}

Типичные задачи при обновлении:

  1. Реагировать на изменение props
  2. Пересчитывать производные данные
  3. Обновлять DOM в зависимости от изменений
  4. Отправлять аналитику

Фаза 3. Размонтирование (Unmounting)

Что происходит:

  1. Компонент удаляется из DOM
  2. Выполняется функция очистки

Как это выглядит в коде:

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

  useEffect(() => {
    // Настраиваем таймер при монтировании
    const interval = setInterval(() => {
      setSeconds(prev => prev + 1);
    }, 1000);

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

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

Типичные задачи при размонтировании:

  1. Остановка таймеров (setInterval, setTimeout)
  2. Отписка от событий (removeEventListener)
  3. Закрытие соединений (WebSocket)
  4. Отмена запросов (AbortController)
  5. Очистка подписок

useEffect с зависимостями — подробно

Пустой массив [] — один раз при монтировании:

useEffect(() => {
  // Выполняется один раз
  console.log('Только при монтировании');
}, []);

Массив с переменными — при их изменении:

useEffect(() => {
  console.log(`Изменился пропс userId: ${userId}`);
}, [userId]);

Несколько зависимостей:

useEffect(() => {
  console.log(`userId или userRole изменились`);
}, [userId, userRole]);

Без массива — при каждом рендере (используй осторожно!):

useEffect(() => {
  console.log('Выполняется при каждом рендере');
}); // Нет массива зависимостей

Частые сценарии использования useEffect

1. Загрузка данных при монтировании:

function UserList() {
  const [users, setUsers] = useState([]);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    const fetchUsers = async () => {
      const response = await fetch('/api/users');
      const data = await response.json();
      setUsers(data);
      setLoading(false);
    };
    
    fetchUsers();
  }, []); // Загружаем только один раз

  if (loading) return <p>Загрузка...</p>;
  return <div>{users.map(user => <p key={user.id}>{user.name}</p>)}</div>;
}

2. Подписка на событие с очисткой:

function WindowWidth() {
  const [width, setWidth] = useState(window.innerWidth);

  useEffect(() => {
    const handleResize = () => setWidth(window.innerWidth);
    
    window.addEventListener('resize', handleResize);
    
    return () => {
      window.removeEventListener('resize', handleResize);
    };
  }, []);

  return <p>Ширина окна: {width}px</p>;
}

3. Автосохранение при изменении данных:

function NoteEditor() {
  const [content, setContent] = useState('');

  useEffect(() => {
    // Автосохраняем каждые 3 секунды при изменении content
    const timer = setTimeout(() => {
      localStorage.setItem('note', content);
      console.log('Заметка сохранена');
    }, 3000);

    return () => clearTimeout(timer);
  }, [content]);

  return <textarea value={content} onChange={(e) => setContent(e.target.value)} />;
}

Сравнение с классовыми компонентами

Для понимания legacy-кода:

class UserProfile extends React.Component {
  // 1. Монтирование
  componentDidMount() {
    console.log('Компонент смонтирован');
    this.loadData();
  }

  // 2. Обновление
  componentDidUpdate(prevProps, prevState) {
    if (this.props.userId !== prevProps.userId) {
      console.log('userId изменился');
      this.loadData();
    }
  }

  // 3. Размонтирование
  componentWillUnmount() {
    console.log('Компонент будет удалён');
    clearInterval(this.interval);
  }

  loadData() {
    // Загрузка данных
  }

  render() {
    return <div>Профиль</div>;
  }
}

useEffect и асинхронные операции

Важно: useEffect не может быть асинхронным напрямую:

❌ Неправильно:

useEffect(async () => {
  const data = await fetchData(); // Ошибка!
}, []);

✅ Правильно (создаём функцию внутри):

useEffect(() => {
  const fetchData = async () => {
    const response = await fetch('/api/data');
    const data = await response.json();
    setData(data);
  };
  
  fetchData();
}, []);

✅ Или с IIFE (немедленно вызываемая функция):

useEffect(() => {
  (async () => {
    const data = await fetchData();
    setData(data);
  })();
}, []);

Частые ошибки новичков

Ошибка Почему Исправление
Забывают про очистку Таймеры/подписки продолжают работать Всегда возвращай функцию очистки
Пустой массив при использовании props Не реагирует на изменения Добавь props в зависимости
Бесконечный цикл Обновляешь state без зависимостей Добавь зависимости или проверь логику
Асинхронный useEffect напрямую async не поддерживается Создай функцию внутри
Используют useEffect для всего Избыточное количество эффектов Используй только когда нужно

Итог (что мы узнали)

Что узнали Как это работает
Жизненный цикл Монтирование → Обновление → Размонтирование
useEffect Главный хук для управления жизненным циклом
Пустой массив [] Выполнить один раз при монтировании
Зависимости Выполнять при изменении переменных
Функция очистки Выполняется при размонтировании
Асинхронные операции Создавать функцию внутри useEffect

Задание для закрепления (на 1–2 часа)

Создай приложение с компонентом User, который:

  1. При монтировании родителя загружает данные случайного пользователя из локального массива users (используй setTimeout для имитации запроса на 2 секунды)
  2. Показывает компонент Loading с текстом «Загрузка…» и анимированным спиннером во время загрузки
  3. После загрузки показывает: имя, фамилию, e-mail, пол, возраст, цвет глаз, телефон
  4. По кнопке «Следующий» — перезагружает данные, снова показывая загрузку; новый пользователь всегда должен отличаться от текущего
  5. При размонтировании (и при новом запросе, начатом до завершения предыдущего) отменяет запрос — очищай таймер в функции очистки useEffect, чтобы не было утечек и «догоняющих» обновлений состояния

Дополнительно: вынеси кнопку в отдельный компонент Button, принимающий onClick и children. Состояние загрузки не храни отдельным флагом — выводи его из данных, чтобы флаг не мог рассинхронизироваться с результатом.

Проверка: нажимай «Следующий» несколько раз подряд, в том числе не дожидаясь окончания загрузки — должен отображаться результат только последнего запроса, а пользователь не должен повторяться дважды подряд. Запусти приложение в StrictMode и убедись, что двойной вызов эффекта не ломает логику.

Пример выполнения задания

Скачать код выполненного задания с GitHub: (откроется в новой вкладке)Жизненный цикл компонента

Что дальше?

В следующем уроке разберём Lists and Keys — как работать с массивами данных в React. Ты научишься рендерить списки, добавлять и удалять элементы, правильно использовать ключи (key) и оптимизировать отображение больших данных. Это один из самых частых сценариев в реальных приложениях.

Теги: