Життєвий цикл компонента React.js

Життєвий цикл компонента React.js

  • 19 вересня
  • читати 20 хв
Володимир Шайтан
Володимир Шайтан Technical Lead у Zoot, Викладач Комп'ютерної школи Hillel.
Віталія Яременко
Віталія Яременко Trainee Frontend Developer у JunJun

React — це популярний компонентний фреймворк, який дозволяє створювати складні інтерфейси, розділяючи їх на невеликі, незалежні частини — компоненти. Компонент у React — це як окремий музикант в оркестрі: він виконує свою роль, виводячи текст, обробляючи події або керуючи станом, і робить свій вклад у загальну гармонію додатка.

Як і будь-який музикант, компоненти в React мають свій життєвий цикл. Вони «народжуються» на сцені, розвивають свою партію, змінюються в процесі виконання, і, врешті-решт, покидають сцену. Розуміння цього циклу є ключем до правильної взаємодії з компонентами, що дозволяє забезпечити їхню стабільну й ефективну роботу.

У цій статті ми розглянемо, що саме відбувається з компонентом на кожному етапі його життєвого циклу в React, і як ви можете впливати на цей процес, щоб досягти бажаних результатів.

КЛАСОВІ ТА ФУНКЦІОНАЛЬНІ КОМПОНЕНТИ: ЩО ЦЕ І ЧИМ ВОНИ ВІДРІЗНЯЮТЬСЯ?  

У React компоненти можна розділити на дві основні категорії: класові та функціональні компоненти. Кожен із них має свої особливості, підходи до управління станом і методами життєвого циклу, а також свої переваги залежно від конкретного випадку використання.

Класові компоненти

Класові компоненти були основним методом створення компонентів у React до введення хуків у версії 16.8. Вони визначаються як класи, що наслідують від React.Component. Це дозволяє їм мати власний стан (state), який зберігається у властивості state, та доступ до методів життєвого циклу, таких як componentDidMount, shouldComponentUpdate й інші.

Переваги класових компонентів:

  1. Повний доступ до методів життєвого циклу: класові компоненти дозволяють детально керувати тим, що відбувається з компонентом на різних етапах його життя, використовуючи методи життєвого циклу.
  2. Звична синтаксична конструкція: для розробників, які звикли до об’єктноорієнтованого програмування, класові компоненти можуть здаватися більш інтуїтивними, оскільки вони слідують класичній структурі ООП.

Функціональні компоненти

Функціональні компоненти були спочатку створені для реалізації простих компонентів без стану. Однак із введенням хуків (hooks) у версії React 16.8, вони стали повноцінною альтернативою класовим компонентам. Тепер функціональні компоненти можуть не лише відображати дані, але й керувати станом (useState) й обробляти побічні ефекти (useEffect).

Переваги функціональних компонентів:

  1. Простота і лаконічність: функціональні компоненти зазвичай містять менше коду й більш зрозумілі, особливо коли йдеться про компоненти без складної логіки.
  2. Легке управління станом і побічними ефектами: хуки, такі як useState та useEffect, дозволяють додавати стан і побічні ефекти без використання класової конструкції, роблячи код більш декларативним та гнучким.
  3. Краща підтримка й повторне використання: функціональні компоненти легко перетворюються на звичайні функції JavaScript, що робить їх більш універсальними та простими у використанні.

ПОРІВНЯННЯ ТА ВИБІР

Хоча класові та функціональні компоненти можуть робити схожі речі, функціональні компоненти з хуками зараз зазвичай є кращим вибором. Вони дозволяють писати менш громіздкий і зрозуміліший код.

Коли вибирати класові компоненти:

  • Якщо у вас вже багато класових компонентів у проєкті, і ви хочете зберегти узгодженість.
  • Якщо вам потрібен повний контроль над методами життєвого циклу.

Коли вибирати функціональні компоненти:

  • Якщо ви починаєте новий проєкт або додаєте нову функціональність — функціональні компоненти з хуками забезпечують сучасний і чистий підхід.
  • Якщо хочете скоротити обсяг коду і зробити його зрозумілішим.

Врешті-решт, вибір між класовими та функціональними компонентами залежить від вашого проєкту й уподобань. Сьогодні функціональні компоненти стають стандартом для створення інтерфейсів у React.

ФАЗИ ЖИТТЄВОГО ЦИКЛУ КОМПОНЕНТА ТА ЇХ МЕТОДИ

Всього життєвий цикл компонента налічує 3 основні фази, це: Монтування (Mounting), Оновлення (Updating) і Розмонтування (Unmounting). Про них далі й поговоримо.

Скорочення:

  • CC - Class component
  • FC - Functional component

ПОГОВОРИМО ПРО КЛАСОВІ КОМПОНЕНТИ

[ CC ]: ФАЗА МОНТУВАННЯ КОМПОНЕНТА (MOUNTING)

Фаза монтування — це початок життєвого циклу компонента в React. На цьому етапі компонент створюється і вперше вставляється в DOM. Цей процес складається з кількох важливих кроків, під час яких викликаються спеціальні методи життєвого циклу.

Виклик constructor()

Перший метод, який викликається при створенні класового компонента, — це його конструктор. Це місце, де ми зазвичай ініціалізуємо початковий стан компонента та прив'язуємо методи до контексту компонента, якщо це необхідно.

Основні задачі конструктора:

  • ініціалізація стану (використання this.state);
  • прив'язка методів до контексту (якщо необхідно);
  • використання props для початкових налаштувань.
class MyComponent extends React.Component {
   constructor(props) {
       super(props);
       this.state = { count: 0 }; // Ініціалізація стану
       this.handleClick = this.handleClick.bind(this); // Прив'язка методу
   }
   handleClick() {
       this.setState({ count: this.state.count + 1 });
   }
}

Виклик static getDerivedStateFromProps()

Після конструктора, якщо компонент отримує нові пропси, може бути викликаний метод static getDerivedStateFromProps. Цей метод дозволяє синхронізувати стан компонента з новими пропсами до того, як відбудеться рендеринг.

Основні задачі getDerivedStateFromProps:

  • синхронізація стану з пропсами, коли вони змінюються.
static getDerivedStateFromProps(nextProps, prevState) {
   if (nextProps.someValue !== prevState.someValue) {
       return { someValue: nextProps.someValue };
   }
   return null;
}

Виклик render()

Метод render() — це серце будь-якого компонента, оскільки саме він визначає, як виглядатиме компонент в інтерфейсі. У ньому повертається JSX, який перетворюється на HTML і вставляється в DOM.

Основні задачі render():

  • повернення структури JSX, яка буде відображена в DOM
render() {
   return <div>Кількість: {this.state.count}</div>;
}

Виклик componentDidMount()

Після того, як компонент був вставлений в DOM, викликається метод componentDidMount. Це ідеальне місце для виконання будь-яких побічних ефектів, таких як запити до сервера або інтеграція зі сторонніми бібліотеками, адже тепер у нас є доступ до відмальованого DOM.

Основні задачі componentDidMount:

  • виконання дій, що вимагають доступу до DOM (наприклад, робота з елементами або ініціалізація анімацій);
  • запити на сервер для отримання даних;
  • встановлення таймерів, підписок тощо.
componentDidMount() {
   fetch('/api/data')
       .then(response => response.json())
       .then(data => this.setState({ data }));
}

[ CC ]: ФАЗА ОНОВЛЕННЯ КОМПОНЕНТА (UPDATING)

Фаза оновлення настає, коли компонент отримує нові пропси або коли змінюється його стан. Розгляньмо, які методи викликаються на цьому етапі.

Виклик static getDerivedStateFromProps()

Цей метод уже був описаний у фазі монтування, але він також може викликатися і на етапі оновлення, якщо компонент отримує нові пропси, щоб синхронізувати їх зі станом компонента.

Виклик shouldComponentUpdate()

Після того, як нові пропси або стан компонента змінилися, React викликає метод shouldComponentUpdate. Це місце, де можна вирішити, чи повинен компонент перерендеритися, що допомагає оптимізувати продуктивність.

  • Основні задачі shouldComponentUpdate:
    • прийняття рішення про необхідність повторного рендерингу компонента.
shouldComponentUpdate(nextProps, nextState) {
   return nextProps.someValue !== this.props.someValue;
}

Виклик render()

Як і у фазі монтування, render() викликається для оновлення DOM у відповідь на зміну пропсів або стану.

Виклик getSnapshotBeforeUpdate()

Перед тим, як DOM буде оновлений, викликається метод getSnapshotBeforeUpdate. Він дає можливість зберегти якусь інформацію перед тим, як зміни будуть застосовані.

  • Основні задачі getSnapshotBeforeUpdate:
    • збереження стану компонента або його оточення перед оновленням (наприклад, позиція прокрутки)
getSnapshotBeforeUpdate(prevProps, prevState) {
   if (prevProps.listLength < this.props.listLength) {
       return this.listRef.scrollHeight;
   }
   return null;
}

Виклик componentDidUpdate()

Після того, як компонент був оновлений, викликається метод componentDidUpdate. Це місце, де можна виконати дії, пов'язані з реакцією на зміни.

  • Основні задачі componentDidUpdate:
    • виконання дій, що залежать від результатів оновлення (наприклад, новий запит до сервера на основі оновлених даних);
    • робота з DOM після оновлення.
componentDidUpdate(prevProps, prevState, snapshot) {
   if (snapshot !== null) {
       this.listRef.scrollTop += this.listRef.scrollHeight - snapshot;
   }
}

[ CC ]: ФАЗА РОЗМОНТУВАННЯ КОМПОНЕНТА (UNMOUNTING)

Фаза розмонтування настає, коли компонент видаляється з DOM. Тут викликається лише один метод, який відповідає за очищення ресурсів, пов'язаних із компонентом.

Виклик componentWillUnmount()

Перед тим, як компонент буде видалений з DOM, викликається метод componentWillUnmount. Це місце для очищення ресурсів, таких як таймери, підписки або скасування мережевих запитів.

Основні задачі componentWillUnmount:

  • очищення всіх підписок, таймерів або будь-яких сторонніх ресурсів, що використовував компонент
componentWillUnmount() {
   clearInterval(this.timerID);
}

ПОГОВОРИМО ПРО ФУНКЦІОНАЛЬНІ КОМПОНЕНТИ

[ FC ] ФАЗА МОНТУВАННЯ (MOUNTING)

Фаза монтування — це момент, коли компонент вперше створюється і вставляється в DOM. У функціональних компонентах ця фаза оброблюється за допомогою хуків useState та useEffect.

Ініціалізація стану за допомогою useState

Функціональні компоненти не мають конструктора, як класові компоненти, але можуть використовувати хук useState для ініціалізації й управління станом.

Що відбувається:

  • useState викликається один раз під час першого рендерингу компонента;
  • useState повертає поточне значення стану та функцію для його оновлення.

Початкове значення стану можна передати як аргумент у useState.

import React, { useState } from 'react';
function MyComponent() {
   const [count, setCount] = useState(0); // Ініціалізація стану
   return (
       <div>
           <p>Кількість: {count}</p>
           <button onClick={() => setCount(count + 1)}>Збільшити</button>
       </div>
   );
}

Виконання побічних ефектів за допомогою useEffect

Після того, як компонент був вставлений в DOM, викликається useEffect. Це аналог методів componentDidMount та componentDidUpdate з класових компонентів.

Що відбувається:

  • useEffect виконується після того, як рендеринг завершено і зміни застосовано до DOM;
  • якщо в useEffect вказано пустий масив залежностей ([ ]), ефект виконається лише один раз після першого рендерингу, що еквівалентно componentDidMount;
  • useEffect також може повернути функцію для очищення, яка виконується під час фази розмонтування.
import React, { useState, useEffect } from 'react';
function MyComponent() {
   const [data, setData] = useState(null);
   useEffect(() => {
       // Виконання запиту до API після монтування
       fetch('https://api.example.com/data')
           .then(response => response.json())
           .then(data => setData(data));
       // Очищення (еквівалент `componentWillUnmount`)
       return () => {
           console.log('Компонент розмонтовано');
       };
   }, []); // Пустий масив залежностей означає, що ефект виконається лише раз
   return (
       <div>
           {data ? <p>Дані: {JSON.stringify(data)}</p> : <p>Завантаження...</p>}
       </div>
   );
}
  • Деталі:

useEffect з пустим масивом залежностей працює як componentDidMount і виконується один раз після монтування.

Якщо масив залежностей містить якісь змінні, useEffect виконується кожен раз, коли ці змінні змінюються.

[ FC ] ФАЗА ОНОВЛЕННЯ (UPDATING)

Фаза оновлення настає, коли змінюються пропси або стан компонента, що призводить до повторного рендерингу. Функціональні компоненти обробляють цю фазу також через хук useEffect і зміну стану за допомогою useState.

Оновлення стану за допомогою useState

Коли викликається функція, яка змінює стан, компонент перерендерюється.

Що відбувається:

  • коли викликається функція, що повертається useState, вона оновлює стан;
  • це призводить до повторного виклику функціонального компонента й рендерингу з новим станом.
function MyComponent() {
   const [count, setCount] = useState(0);
   const handleClick = () => {
       setCount(count + 1); // Оновлення стану викликає рендеринг
   };
   return (
       <div>
           <p>Кількість: {count}</p>
           <button onClick={handleClick}>Збільшити</button>
       </div>
   );
}

Виконання побічних ефектів на оновлення за допомогою useEffect

useEffect викликається не тільки після першого рендерингу, але й після кожного оновлення компонента, якщо змінюється одна із залежностей, вказаних у масиві.

Що відбувається:

  • якщо в масиві залежностей useEffect вказано змінні, він буде викликаний кожен раз, коли ці змінні змінюються;
  • це дає змогу виконувати певні дії при зміні пропсів або стану.
import React, { useState, useEffect } from 'react';
function MyComponent({ someProp }) {
   const [data, setData] = useState(null);
   useEffect(() => {
       // Ефект виконується кожного разу, коли змінюється `someProp`
       fetch(`https://api.example.com/data?prop=${someProp}`)
           .then(response => response.json())
           .then(data => setData(data));
   }, [someProp]); // Ефект залежить від `someProp`
   return (
       <div>
           <p>Дані: {data ? JSON.stringify(data) : 'Завантаження...'}</p>
       </div>
   );
}
  • Деталі:

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

[ FC ] ФАЗА РОЗМОНТУВАННЯ (UNMOUNTING)

Фаза розмонтування відбувається, коли компонент видаляється з DOM. У функціональних компонентах ця фаза обробляється через useEffect, коли ви повертаєте функцію очищення.

Виконання очищення за допомогою useEffect

Функція, яку повертає useEffect, виконується під час фази розмонтування. Це дозволяє очистити ресурси, наприклад, скасувати таймери або запити, і уникнути витоків пам’яті.

Що відбувається:

  • коли компонент видаляється з DOM, React викликає функцію очищення, повернену з useEffect;
  • це дозволяє зупинити всі процеси, які були запущені в цьому ефекті.
import React, { useState, useEffect } from 'react';
function MyComponent() {
   const [count, setCount] = useState(0);
   useEffect(() => {
       const timer = setInterval(() => {
           setCount(prevCount => prevCount + 1);
       }, 1000);
       // Функція очищення
       return () => {
           clearInterval(timer); // Очищення таймера при розмонтуванні компонента
       };
   }, []); // Ефект виконується один раз, при монтуванні
   return (
       <div>
           <p>Кількість: {count}</p>
       </div>
   );
}
  • Деталі:

Використання очищення через useEffect дозволяє зупинити запущені процеси, коли компонент більше не потрібен, наприклад, зупинити таймер або очистити підписки.

ВИСНОВОК

Життєвий цикл компонентів у React — це як «залаштункова магія», яка допомагає вашим додаткам працювати плавно й ефективно. Незалежно від того, чи ви фанат старої школи з класовими компонентами, чи обожнюєте нові функціональні компоненти з хуками, розуміння життєвого циклу дає вам суперсилу контролювати, що відбувається на кожному етапі життя ваших компонентів.

Класові компоненти дають вам повний доступ до життєвого циклу, що іноді корисно, якщо ви хочете мати абсолютний контроль над усім. Вони добре підходять для старих проєктів або коли потрібно дотримуватися традиційного підходу.

Але якщо ви хочете створювати легкий і зрозумілий код, функціональні компоненти — це те, що потрібно. Вони простіші, компактніші, і все завдяки хукам, таким як `useState` та `useEffect`. Функціональні компоненти зараз стали стандартом у React, і це не просто так.

Тож вибір між класовими та функціональними компонентами залежить від ваших задач і стилю роботи. Але знаючи, як керувати життєвим циклом у будь-якому з них, ви зможете створювати додатки, які будуть працювати гладко і без зайвих клопотів.

ПОРАДИ ДЛЯ ВИКОРИСТАННЯ

1. Використовуйте хуки для управління станом і побічними ефектами

У функціональних компонентах завжди використовуйте `useState` для управління станом і `useEffect` для обробки побічних ефектів. Це дозволяє тримати код чистим і зрозумілим, а також спрощує логіку компонента. Старайтеся групувати пов’язані побічні ефекти разом, щоб код був структурованим.

2. Мінімізуйте кількість рендерингів

Завжди намагайтеся мінімізувати кількість рендерингів компонента. Використовуйте `shouldComponentUpdate` у класових компонентах або хуки, такі як `useMemo` і `useCallback`, щоб уникнути непотрібних повторних рендерингів і тим самим покращити продуктивність додатка.

3. Правильно керуйте залежностями в `useEffect`

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

4. Очищайте ресурси у фазі розмонтування

Не забувайте про очищення ресурсів у фазі розмонтування. Завжди повертайте функцію очищення з `useEffect`, щоб прибрати таймери, підписки або скасувати запити, коли компонент видаляється з DOM. Це допоможе уникнути витоків пам'яті й інших проблем.

5. Використовуйте умовне рендерування для складних інтерфейсів

Для складних інтерфейсів використовуйте умовне рендерування, щоб уникнути непотрібного коду в DOM. Це дозволяє відображати лише ті елементи, які потрібні наразі, що полегшує підтримку коду та покращує продуктивність.

Рекомендуємо публікації по темі