> Как работает жизненный цикл React-компонентов (React)

Уровень: senior · Роль: frontend · Категория: Технические вопросы

Компании: Инрэко, Точка, Kvando Technologies, ООО Премиум ИТ Солюшен, Kodix, Spotware, Aston, IT-One, EPAM, Сбер, SmartWay

Стек: React

> Пример ответа

Короткий ответ

Жизненный цикл React-компонентов - это последовательность фаз: монтирование, обновление и размонтирование. В классовых компонентах используются методы componentDidMount, componentDidUpdate, componentWillUnmount. В функциональных - useEffect с массивами зависимостей, который эмулирует эти фазы. React 18 с Concurrent Mode изменил приоритеты, но базовые фазы остались. Понимание цикла критично для управления побочными эффектами, оптимизации ререндеров и предотвращения утечек памяти.

Подробное объяснение

Жизненный цикл React-компонентов делится на три основные фазы:

  1. Монтирование (Mounting) - компонент вставляется в DOM. В классовых: constructor → static getDerivedStateFromProps → render → componentDidMount. В функциональных: тело функции (инициализация state/refs) → return JSX → useEffect с пустыми зависимостями (аналог componentDidMount).

  2. Обновление (Updating) - при изменении props или state. Классовые: getDerivedStateFromProps → shouldComponentUpdate → render → getSnapshotBeforeUpdate → componentDidUpdate. Функциональные: повторный вызов функции → useEffect с зависимостями (аналог componentDidUpdate, но с очисткой предыдущего эффекта).

  3. Размонтирование (Unmounting) - удаление из DOM. Классовые: componentWillUnmount. Функциональные: return-функция внутри useEffect (cleanup).

Ключевые нюансы для senior:

  • В строгом режиме (StrictMode) React дважды вызывает монтирование в dev для детекции side effects.
  • Concurrent Mode позволяет React прерывать рендер, что ломает предположения о синхронности lifecycle.
  • useLayoutEffect синхронно выполняется после мутаций DOM, но до отрисовки браузером - аналог componentDidMount/componentDidUpdate.
  • Error boundaries ловят ошибки в lifecycle методах (кроме event handlers и async code).

На практике

Для senior важно понимать trade-offs:

  • componentWillMount (устарел) - не используйте, переносите логику в constructor или componentDidMount.
  • shouldComponentUpdate или React.memo - для оптимизации, но не злоупотребляйте, сравнивайте только при реальных проблемах с производительностью.
  • getDerivedStateFromProps - редкий случай, обычно лучше lift state up или использовать key.
  • В функциональных компонентах избегайте бесконечных циклов в useEffect - проверяйте зависимости.
  • Для подписок (WebSocket, event listeners) всегда используйте cleanup в useEffect, иначе утечки памяти.

Пример кода

JSX
// Классовый компонент
class DataFetcher extends React.Component {
state = { data: null, loading: true };
componentDidMount() {
this.fetchData();
}
componentDidUpdate(prevProps) {
if (prevProps.url !== this.props.url) {
this.fetchData();
}
}
componentWillUnmount() {
this.abortController?.abort();
}
fetchData() {
this.abortController = new AbortController();
fetch(this.props.url, { signal: this.abortController.signal })
.then(res => res.json())
.then(data => this.setState({ data, loading: false }))
.catch(err => {
if (err.name !== 'AbortError') console.error(err);
});
}
render() {
return this.state.loading ? <Spinner /> : <div>{this.state.data}</div>;
}
}
// Функциональный эквивалент
function DataFetcher({ url }) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
const abortController = new AbortController();
setLoading(true);
fetch(url, { signal: abortController.signal })
.then(res => res.json())
.then(data => {
setData(data);
setLoading(false);
})
.catch(err => {
if (err.name !== 'AbortError') console.error(err);
});
return () => abortController.abort();
}, [url]);
return loading ? <Spinner /> : <div>{data}</div>;
}

Как отвечать на собеседовании

Начните с чёткого разделения классовых и функциональных подходов, упомяните Concurrent Mode как современный контекст. Покажите понимание не только фаз, но и практических проблем: утечки памяти, лишние ререндеры, race conditions в асинхронных запросах. Приведите пример, когда useLayoutEffect предпочтительнее useEffect (например, измерение DOM). Избегайте заучивания всех методов - сфокусируйтесь на ключевых и их заменах в хуках. Если спросят про StrictMode, объясните его назначение для выявления побочных эффектов.

Что проверяет интервьюер

  • Глубину понимания lifecycle, а не поверхностное знание методов.
  • Умение переключаться между классовым и функциональным подходами.
  • Понимание Concurrent Mode и его влияния на lifecycle (например, что render может быть вызван несколько раз).
  • Знание оптимизаций (shouldComponentUpdate, memo, useMemo) и их ограничений.
  • Практический опыт с побочными эффектами, подписками и очисткой.

Типичные ошибки

  • Путаница между componentDidMount и useEffect с пустыми зависимостями - забывают, что useEffect выполняется после каждого рендера, если не указать зависимости.
  • Игнорирование cleanup в useEffect - приводит к утечкам памяти и двойным запросам.
  • Использование getDerivedStateFromProps без необходимости - лучше управлять состоянием через key или lifting state up.
  • Предположение, что lifecycle методы синхронны в Concurrent Mode - React может отложить commit фазу.
  • Забывают, что componentWillUnmount не вызывается при ошибке в рендере - используйте error boundaries.

> Похожие задачи по frontend

> ГОТОВЫ К СЛЕДУЮЩЕМУ СОБЕСЕДОВАНИЮ?

Запустите тренировочную сессию с ИИ и получите детальную обратную связь, чтобы увереннее проходить реальные интервью