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

Какой жизненный цикл функциональных компонентов?

2.0 Middle🔥 211 комментариев
#React

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

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

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

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

Это один из ключевых вопросов для понимания React. Функциональные компоненты имеют жизненный цикл, но он сильно отличается от классовых компонентов. Вместо методов жизненного цикла используются хуки.

Жизненный цикл функционального компонента

Функциональный компонент проходит через три фазы:

1. MOUNTING (монтирование)
2. UPDATING (обновление)
3. UNMOUNTING (размонтирование)

1. MOUNTING (Монтирование)

Эта фаза происходит, когда компонент впервые создается и вставляется в DOM.

function MyComponent({ initialValue }) {
  // 1. Тело функции вызывается первый раз
  console.log('Тело компонента вызвано');

  const [count, setCount] = React.useState(initialValue);
  // 2. Хуки инициализируются

  React.useEffect(() => {
    // 3. После рендера компонента этот код выполняется
    console.log('Компонент смонтирован');
    
    // 4. Опционально: вернуть cleanup функцию
    return () => {
      console.log('Cleanup перед демонтированием');
    };
  }, []); // Пустые зависимости = выполнить один раз при монтировании

  return <div>Count: {count}</div>;
}

Порядок при монтировании:

  1. Вызов функции компонента
  2. Инициализация состояния (useState)
  3. Рендер в Virtual DOM
  4. Обновление реального DOM
  5. Выполнение useEffect с пустыми зависимостями

2. UPDATING (Обновление)

Эта фаза происходит, когда компонент обновляется из-за:

  • Изменения состояния (setState)
  • Изменения props
  • Обновления контекста
  • Принудительного обновления (редко)
function MyComponent({ userId }) {
  const [userData, setUserData] = React.useState(null);
  const [posts, setPosts] = React.useState([]);

  // Этот effect выполнится при монтировании И при изменении userId
  React.useEffect(() => {
    console.log(`Загружаю данные для пользователя ${userId}`);
    
    fetchUser(userId).then(setUserData);
    fetchPosts(userId).then(setPosts);

    return () => {
      console.log('Cleanup для userId:', userId);
      // Отменить запросы, если нужно
    };
  }, [userId]); // Зависимость от userId

  // Этот effect выполняется при КАЖДОМ обновлении
  React.useEffect(() => {
    console.log('Компонент обновился');
  }); // Без массива зависимостей

  return (
    <div>
      {userData && <h1>{userData.name}</h1>}
      <ul>
        {posts.map(post => (
          <li key={post.id}>{post.title}</li>
        ))}
      </ul>
    </div>
  );
}

Порядок при обновлении:

  1. Вызов функции компонента с новыми props/state
  2. Выполнение cleanup функции из предыдущего effect (если есть)
  3. Рендер в Virtual DOM
  4. Сравнение с предыдущим Virtual DOM (diffing)
  5. Обновление реального DOM (если что-то изменилось)
  6. Выполнение useEffect с учетом зависимостей

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

Эта фаза происходит, когда компонент удаляется из DOM.

function MyComponent() {
  React.useEffect(() => {
    // Монтирование: подписываемся на события
    const handleResize = () => {
      console.log('Окно изменило размер');
    };
    window.addEventListener('resize', handleResize);

    // Размонтирование: отписываемся от событий (cleanup)
    return () => {
      console.log('Компонент размонтирован');
      window.removeEventListener('resize', handleResize);
    };
  }, []);

  return <div>Resizable</div>;
}

Порядок при размонтировании:

  1. React помечает компонент как "собирается удалить"
  2. Выполняет все cleanup функции из useEffect
  3. Удаляет элемент из реального DOM
  4. Очищает память (если нет ссылок на компонент)

Полный пример жизненного цикла

function Counter() {
  const [count, setCount] = React.useState(0);
  const [name, setName] = React.useState('');

  console.log('1. Рендер компонента (count = ' + count + ')');

  // Effect 1: выполняется при монтировании и при изменении count
  React.useEffect(() => {
    console.log('3. Effect 1: count изменился на', count);

    return () => {
      console.log('3.5. Cleanup effect 1: count был', count);
    };
  }, [count]);

  // Effect 2: выполняется при монтировании и при изменении name
  React.useEffect(() => {
    console.log('3. Effect 2: name изменилось на', name);

    return () => {
      console.log('3.5. Cleanup effect 2: name был', name);
    };
  }, [name]);

  // Effect 3: выполняется один раз при монтировании
  React.useEffect(() => {
    console.log('3. Effect 3: компонент смонтирован');

    return () => {
      console.log('3.5. Cleanup effect 3: компонент размонтируется');
    };
  }, []);

  const handleClick = () => {
    console.log('2. Клик по кнопке');
    setCount(count + 1);
  };

  const handleNameChange = (e) => {
    console.log('2. Имя изменилось');
    setName(e.target.value);
  };

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={handleClick}>Увеличить</button>
      <input value={name} onChange={handleNameChange} placeholder="Имя" />
    </div>
  );
}

Логика выполнения:

При первом монтировании:

1. Рендер компонента (count = 0)
3. Effect 1: count изменился на 0
3. Effect 2: name изменилось на
3. Effect 3: компонент смонтирован

Часто задаваемый вопрос: почему Effect 1 выполнился с count = 0? Ответ: useEffect с зависимостью [count] запускается и при монтировании, и при изменении count.

После клика (setCount(1)):

1. Рендер компонента (count = 1)
3.5. Cleanup effect 1: count был 0
3. Effect 1: count изменился на 1

После изменения имени:

1. Рендер компонента (count = 1)
3.5. Cleanup effect 2: name был (пустое)
3. Effect 2: name изменилось на (новое значение)

При размонтировании:

3.5. Cleanup effect 1: count был 1
3.5. Cleanup effect 2: name был текущее значение
3.5. Cleanup effect 3: компонент размонтируется

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

// Классовый компонент (старый подход)
class Counter extends React.Component {
  constructor(props) {
    super(props);
    this.state = { count: 0 };
  }

  componentDidMount() {
    // Вызывается один раз после монтирования
    console.log('Компонент смонтирован');
  }

  componentDidUpdate(prevProps, prevState) {
    // Вызывается после каждого обновления
    if (prevState.count !== this.state.count) {
      console.log('Count изменился');
    }
  }

  componentWillUnmount() {
    // Вызывается перед размонтированием
    console.log('Компонент размонтируется');
  }

  render() {
    return (
      <button onClick={() => this.setState({ count: this.state.count + 1 })}>
        Count: {this.state.count}
      </button>
    );
  }
}

// Функциональный компонент (современный подход)
function Counter() {
  const [count, setCount] = React.useState(0);

  React.useEffect(() => {
    console.log('Компонент смонтирован');
    
    return () => {
      console.log('Компонент размонтируется');
    };
  }, []);

  React.useEffect(() => {
    if (count > 0) {
      console.log('Count изменился');
    }
  }, [count]);

  return (
    <button onClick={() => setCount(count + 1)}>
      Count: {count}
    </button>
  );
}

Лучшие практики

1. Всегда очищай побочные эффекты

React.useEffect(() => {
  const interval = setInterval(() => {
    console.log('Тик');
  }, 1000);

  // Обязательно очистить интервал
  return () => clearInterval(interval);
}, []);

2. Используй правильные зависимости

// Неправильно: забыли зависимость
React.useEffect(() => {
  console.log(userId); // userId может быть устарелым!
}, []); // <- Забыли userId

// Правильно:
React.useEffect(() => {
  console.log(userId);
}, [userId]); // <- Добавили зависимость

3. Группируй логику по сценариям

// Один effect для инициализации
React.useEffect(() => {
  // ...
}, []);

// Другой effect для синхронизации с props
React.useEffect(() => {
  // ...
}, [dependency]);

// Не смешивай разную логику в один effect

Итоги

Жизненный цикл функционального компонента:

  1. Mounting: Инициализация состояния, рендер, выполнение useEffect
  2. Updating: Обновление при изменении state/props, cleanup предыдущего effect, новый effect
  3. Unmounting: Выполнение cleanup функций, удаление из DOM

Хуки (особенно useEffect) заменили методы жизненного цикла и делают код:

  • Более читаемым и модульным
  • Легче переиспользовать логику
  • Проще тестировать
Какой жизненный цикл функциональных компонентов? | PrepBro