Skip to main content

Методи життєвого циклу компонентів у React

Методи життєвого циклу компонентів (component lifecycle methods) в React — це функції класових компонентів, які React викликає автоматично в три конкретні моменти: коли компонент з'являється в DOM, оновлюється або видаляється.

Теорія

TL;DR

  • Три фази: Монтування (компонент створено), Оновлення (змінились props або state), Демонтування (компонент видалено)
  • Порядок при монтуванні: constructorrendercomponentDidMount. Завжди саме так.
  • Побічні ефекти (API-запити, таймери) йдуть у componentDidMount, а не в render чи constructor
  • Хуки відтворюють усі три фази через useEffect з масивом залежностей
  • Новий код: хуки. Існуючі класові компоненти: lifecycle методи

Швидкий приклад

jsx
class Timer extends React.Component { constructor(props) { super(props); this.state = { count: 0 }; console.log('1. Constructor'); } componentDidMount() { // DOM готовий, тут безпечно запускати побічні ефекти this.timer = setInterval(() => { this.setState({ count: this.state.count + 1 }); }, 1000); console.log('3. DidMount - таймер запущено'); } componentWillUnmount() { clearInterval(this.timer); // запобігає витоку пам'яті console.log('Демонтування - таймер зупинено'); } render() { console.log('2. Render'); return <div>Лічильник: {this.state.count}</div>; } } // Виведе при монтуванні: 1. Constructor → 2. Render → 3. DidMount

constructor виконується першим, render будує virtual DOM, а componentDidMount спрацьовує коли реальний DOM готовий. Порядок незмінний.

Три фази

Монтування — коли компонент вперше з'являється в DOM:

МетодКоли викликається
constructorЕкземпляр компонента створено, state ініціалізовано
static getDerivedStateFromPropsПеред кожним render (рідко потрібен)
renderПовертає JSX. Має бути чистою функцією.
componentDidMountПісля того як DOM відмальовано і готовий

Оновлення відбувається коли змінюються props або state:

МетодКоли викликається
static getDerivedStateFromPropsПеред перерендером
shouldComponentUpdateМоже повернути false і пропустити перерендер
renderГенерує оновлений JSX
getSnapshotBeforeUpdateЗчитує DOM перед оновленням (позиція скролу тощо)
componentDidUpdateПісля оновлення DOM, тут безпечно для побічних ефектів

Демонтування має один метод: componentWillUnmount. Спрацьовує перед тим як React видаляє компонент. Тут прибирають таймери, слухачі подій і незавершені запити.

Головна різниця від хуків

Lifecycle методи мають строгий порядок виклику, який забезпечує Fiber reconciler React. render ніколи не виконується під час демонтування. componentDidMount завжди спрацьовує після фіксації DOM. Ця передбачуваність спрощує розуміння складних сценаріїв.

useEffect об'єднує всі три фази в одну функцію. Невірний масив залежностей призводить або до застарілих даних, або до нескінченного перерендеру. Lifecycle методи унеможливлюють цей баг за своєю природою.

Коли що використовувати

  • Початкове завантаження даних: componentDidMount
  • Повторний запит при зміні props: componentDidUpdate з порівнянням prevProps
  • Пропуск зайвих перерендерів: shouldComponentUpdate (або React.memo)
  • Очищення таймерів і підписок: componentWillUnmount
  • Синхронізація state з props: getDerivedStateFromProps (рідко, краще useEffect)
  • Фіксація скролу перед оновленням DOM: getSnapshotBeforeUpdate

Lifecycle методи vs хуки

АспектLifecycle методиХуки (useEffect)
ФазиМонтування / Оновлення / Демонтування (9+ методів)Один ефект з масивом залежностей
ПорядокСтрогий і гарантованийПісля render; залежності керують повторним запуском
ОчищенняcomponentWillUnmountФункція повернення з useEffect
ПродуктивністьshouldComponentUpdate для точного контролюReact.memo + useMemo в React 18+
СкладністьБільше API для запам'ятовуванняПростіше, але залежності бувають каверзними
Коли використовуватиЛегасі кодова базаВесь новий код

Як React виконує це всередині

Fiber reconciler будує робоче дерево під час узгодження (reconciliation). Він може призупиняти роботу щоб дати пріоритет введенню користувача над фоновими оновленнями. Після фіксації дерева в реальному DOM React викликає componentDidMount або componentDidUpdate. Демонтування запускає componentWillUnmount перед видаленням дерева. У конкурентному режимі React 18 ефекти можуть групуватись і відкладатись, що частково пояснює відмінності в поведінці хуків і класових методів.

Типові помилки

API-запит у render:

jsx
// Неправильно - виконується при кожному рендері, блокує UI render() { const data = fetch('/api/todos'); return <div>{data}</div>; } // Правильно componentDidMount() { fetch('/api/todos') .then(res => res.json()) .then(data => this.setState({ data })); }

render має бути чистою функцією. Ніяких асинхронних викликів, ніяких побічних ефектів.

Відсутність очищення в componentWillUnmount:

jsx
componentDidMount() { this.timer = setInterval(tick, 1000); // Без очищення = витік пам'яті при кожному переході } componentWillUnmount() { clearInterval(this.timer); // Саме для цього тут і є }

Відсутність очищення в componentWillUnmount — найпоширеніший витік пам'яті в React-продакшені. У React Router app компоненти монтуються і демонтуються при кожній навігації, тому інтервали і підписки накопичуються.

Виклик setState у componentWillUnmount:

jsx
componentWillUnmount() { this.setState({ done: true }); // Warning: can't update unmounted component }

Компонент вже видалено. React ігнорує оновлення і виводить попередження. Замість цього використовуй булевий прапорець, який перевіряється в componentDidUpdate.

Відсутність перевірки prevProps у componentDidUpdate:

jsx
// Неправильно - нескінченний цикл componentDidUpdate() { this.fetchData(); // спрацьовує після кожного оновлення, включно з тими що він сам спричинює } // Правильно componentDidUpdate(prevProps) { if (this.props.userId !== prevProps.userId) { this.fetchData(); } }

Завжди додавай умову в componentDidUpdate. Без неї — нескінченний цикл перерендерів.

Де зустрічається в реальних проектах

  • React Router: componentDidMount завантажує дані конкретного маршруту (профіль, деталі запису)
  • Redux-Observable: підписка на epic в componentDidMount, відписка в componentWillUnmount
  • Чат-застосунки: getSnapshotBeforeUpdate фіксує позицію скролу, componentDidUpdate відновлює її після завантаження нових повідомлень
  • Next.js: componentDidMount виконується тільки на клієнті, підходить для браузерних API типу localStorage
  • Міграція на React 18: заміна класових lifecycle методів на useEffect відкриває конкурентний рендеринг

Питання на співбесіді

Q: Який порядок монтування для батьківського і дочірнього компонентів?
A: Constructor батька, render батька, constructor дочірнього, render дочірнього, componentDidMount дочірнього, потім componentDidMount батька. Дочірні компоненти завжди монтуються раніше ніж батько завершує монтування.

Q: Чому не варто робити API-запити в constructor?
A: constructor виконується до появи компонента в DOM. Немає гарантій щодо стану DOM, а в SSR (Next.js, Remix) це призводить до помилок гідратації. Правильне місце для запитів — componentDidMount.

Q: Навіщо потрібен getSnapshotBeforeUpdate?
A: Він виконується прямо перед тим як React застосовує зміни до DOM. Можна зчитати поточні значення DOM (позиція скролу) і передати їх у componentDidUpdate третім аргументом. Найпоширеніший кейс: збереження позиції скролу в чаті при завантаженні нових повідомлень вище поточного вьюпорта.

Q: Чи може shouldComponentUpdate спричинити баги?
A: Так. Якщо повернути false помилково, компонент перестає оновлюватись без жодної помилки. Та сама небезпека є в React.memo з кастомним компаратором. Завжди профілюй з React DevTools Profiler перед такою оптимізацією.

Q: У конкурентному режимі React 18 lifecycle методи можуть спрацьовувати кілька разів?
A: render може викликатись декілька разів перед фіксацією. Але componentDidMount і componentDidUpdate все одно спрацьовують один раз за фіксацію. Це одна з причин чому React 18 рекомендує хуки: семантика useEffect чіткіша в конкурентних сценаріях.

Приклади

Базовий: відстеження порядку lifecycle

jsx
class LifecycleDemo extends React.Component { constructor(props) { super(props); this.state = { name: props.name }; console.log('1. constructor'); } static getDerivedStateFromProps(props, state) { console.log('2. getDerivedStateFromProps'); return null; } componentDidMount() { console.log('4. componentDidMount - DOM готовий'); } componentDidUpdate(prevProps, prevState) { console.log('5. componentDidUpdate'); } componentWillUnmount() { console.log('6. componentWillUnmount - очищення тут'); } render() { console.log('3. render'); return <div>Привіт, {this.state.name}</div>; } }

Запусти це у браузері щоб побачити точний порядок. getDerivedStateFromProps спрацьовує перед кожним render, включно з оновленнями. Це здивовує більшість розробників вперше.

Середній: список завдань з API

jsx
class TodoList extends React.Component { state = { todos: [], loading: true }; componentDidMount() { this.fetchTodos(); } componentDidUpdate(prevProps) { // Повторний запит тільки коли змінився filter if (this.props.filter !== prevProps.filter) { this.fetchTodos(); } } fetchTodos() { this.setState({ loading: true }); fetch(`/api/todos?filter=${this.props.filter}`) .then(res => res.json()) .then(todos => this.setState({ todos, loading: false })); } render() { if (this.state.loading) return <div>Завантаження...</div>; return ( <ul> {this.state.todos.map(todo => ( <li key={todo.id}>{todo.text}</li> ))} </ul> ); } }

componentDidMount обробляє початкове завантаження. componentDidUpdate обробляє повторні запити при зміні props без дублювання логіки. Перевірка prevProps запобігає нескінченному циклу.

Складний: пошук з debounce і очищенням

jsx
class SearchResults extends React.Component { state = { results: [] }; componentDidUpdate(prevProps) { if (this.props.query !== prevProps.query) { this.scheduleFetch(); } } scheduleFetch = () => { // Стрілкова функція зберігає правильний контекст this clearTimeout(this.timeout); this.timeout = setTimeout(() => { fetch(`/api/search?q=${this.props.query}`) .then(res => res.json()) .then(data => this.setState({ results: data.results })); }, 300); }; componentWillUnmount() { clearTimeout(this.timeout); // скасовуємо запит якщо компонент зникає } render() { return ( <ul> {this.state.results.map(r => <li key={r.id}>{r.title}</li>)} </ul> ); } }

Два моменти роблять цей код коректним. Debounce виключає запит при кожному натисканні клавіші. Очищення в componentWillUnmount скасовує відкладений таймаут якщо компонент демонтується до закінчення 300мс, запобігаючи setState на демонтованому компоненті.

Коротка відповідь

Для співбесіди
Premium

Коротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.

Дочитали статтю?