Как устроен жизненный цикл компонента?

Жизненный цикл компонента в React и React Native — это набор этапов, через которые проходит компонент от момента его создания до удаления с экрана. Понимание жизненного цикла особенно важно для управления состоянием, запросами к API, подписками, анимациями, кешированием и освобождением ресурсов.

Существует два типа компонентов: классовые и функциональные. У каждого из них есть своя модель жизненного цикла:

  • Классовые компоненты используют специальные методы (componentDidMount, componentDidUpdate и т. д.).

  • Функциональные компоненты используют хуки (useEffect, useLayoutEffect, useState и др.), чтобы имитировать поведение жизненного цикла.

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

Условно делится на 3 фазы:

1. Инициализация

Происходит при создании экземпляра компонента.

  • constructor(props)

    • Вызывается первым.

    • Используется для инициализации состояния через this.state = {}.

    • Также можно привязывать методы: this.handleClick = this.handleClick.bind(this);

2. Монтирование

Компонент добавляется в DOM (на экран).

  • static getDerivedStateFromProps(props, state)

    • Редко используется.

    • Синхронизирует состояние с props перед рендером.

    • Не имеет доступа к this.

  • render()

    • Обязательный метод.

    • Возвращает JSX-разметку.

    • Не должен изменять состояние или вызывать побочные эффекты.

  • componentDidMount()

    • Вызывается один раз после первого рендера.

    • Подходит для запросов к API, подписок, инициализации таймеров.

    • Часто используется для загрузки данных.

3. Обновление

Компонент может обновиться, если:

  • Изменились props

  • Изменилось state

  • Был вызван forceUpdate()

Методы:

  • static getDerivedStateFromProps()

  • shouldComponentUpdate(nextProps, nextState)

    • Позволяет оптимизировать рендер, вернув false, если обновление не требуется.
  • render()

  • getSnapshotBeforeUpdate(prevProps, prevState)

    • Возвращает snapshot (например, позицию прокрутки) перед обновлением DOM.
  • componentDidUpdate(prevProps, prevState, snapshot)

    • Вызывается после обновления.

    • Используется для работы с DOM, повторных запросов на основе новых props и пр.

4. Размонтирование

Компонент удаляется из DOM.

  • componentWillUnmount()
    • Подходит для отмены подписок, очистки таймеров и освобождения ресурсов.

Пример жизненного цикла в классовом компоненте

class ExampleComponent 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('Счётчик обновлён');
}
}
componentWillUnmount() {
console.log('Компонент удалён');
}
render() {
return (
<Button
title="Увеличить"
onPress={() => this.setState({ count: this.state.count + 1 })}
/>
);
}
}

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

В функциональных компонентах жизненный цикл реализуется с помощью хука useEffect.

useEffect как componentDidMount

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

Пустой массив зависимостей означает, что эффект выполнится один раз после монтирования.

useEffect как componentDidUpdate

useEffect(() => {
console.log('state изменился');
}, \[state\]);

Хук с зависимостями выполняется каждый раз, когда указанные значения изменяются.

useEffect как componentWillUnmount

useEffect(() => {
const subscription = someEvent.addListener(() => {});
return () => {
subscription.remove(); // очистка
console.log('Компонент размонтирован');
};
}, \[\]);

Функция, возвращаемая из useEffect, вызывается при размонтировании компонента или перед следующей активацией эффекта.

useEffect комбинирует componentDidMount, componentDidUpdate и componentWillUnmount

Пример:

useEffect(() => {
console.log('Сработал эффект');
return () => {
console.log('Очистка при размонтировании или обновлении');
};
}, \[count\]);

Алгоритм вызовов жизненного цикла

Для классовых компонентов:

  1. constructor

  2. getDerivedStateFromProps

  3. render

  4. componentDidMount

обновление:

  1. getDerivedStateFromProps

  2. shouldComponentUpdate

  3. render

  4. getSnapshotBeforeUpdate

  5. componentDidUpdate

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

  • componentWillUnmount

Визуальное представление жизненного цикла

\[ constructor \]

\[ getDerivedStateFromProps \]

\[ render \]

\[ componentDidMount \]  монтирование завершено

(пользователь взаимодействует, меняются props/state)

\[ getDerivedStateFromProps \]
\[ shouldComponentUpdate \]

\[ render \]

\[ getSnapshotBeforeUpdate \]

\[ componentDidUpdate \]

\[ componentWillUnmount \]  при удалении

Жизненный цикл и асинхронные операции

Примеры задач, которые часто обрабатываются в componentDidMount или useEffect:

  • Получение данных с API (fetch, axios)

  • Установка слушателей событий (например, Keyboard, Dimensions)

  • Запуск анимаций (Animated.timing)

  • Подключение к WebSocket или Firebase

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

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

Частые ошибки и замечания

  • Никогда не вызывайте setState в render, это вызовет бесконечный цикл.

  • componentDidMount вызывается только один раз, даже если props изменяются.

  • В useEffect, если вы не укажете массив зависимостей, эффект будет вызываться после каждого рендера.

  • Если вы используете setInterval или setTimeout, не забудьте их очистить в componentWillUnmount или return () => {...} в useEffect.

Жизненный цикл компонента — это ключевой механизм React и React Native, позволяющий управлять поведением компонентов на разных этапах их существования. Он охватывает монтирование, обновление и размонтирование, и на каждом этапе разработчик может внедрять собственную логику. Понимание этого процесса особенно важно при работе с состоянием, асинхронными запросами, очисткой ресурсов и оптимизацией производительности.