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

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

  • 19 сентября
  • читать 20 мин
Владимир Шайтан
Владимир Шайтан Senior Full Stack Developer в UKEESS Software House, Преподаватель Компьютерной школы Hillel.
Виталия Яременко
Виталия Яременко Trainee Frontend Developer в JunJun

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

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

В этой статье Владимир Шайтан, преподаватель Hillel IT School и выпускница его курса — Віталия Яременко, рассмотрели, что происходит с компонентом на каждом этапе его жизненного цикла в React, и как вы можете влиять на этот процесс, чтобы достичь желаемых результатов.

КЛАССОВЫЕ И ФУНКЦИОНАЛЬНЫЕ КОМПОНЕНТЫ: ЧТО ЭТО И ЧЕМ ОНИ ОТЛИЧАЮТСЯ?

В React компоненты можно разделить на две основные категории: классовые и функциональные компоненты. У каждого из них есть свои особенности, подходы к управлению состоянием и методами жизненного цикла, а также свои преимущества в зависимости от конкретного случая использования.

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

Классовые компоненты были основным методом создания компонентов в React для ввода хуков в версии 16.8. Они определяются как классы, подражающие React.Component. Это позволяет им иметь собственное состояние, которое сохраняется в свойстве state, и доступ к методам жизненного цикла, таким как componentDidMount, shouldComponentUpdate и другие.

Преимущества классовых компонентов:

  1. Полный доступ к методам жизненного цикла: классовые компоненты позволяют подробно управлять происходящим с компонентом на разных этапах его жизни, используя методы жизненного цикла.
  2. Привычная синтаксическая конструкция: для разработчиков, привыкших к объектно-ориентированному программированию, классовые компоненты могут казаться более интуитивными, поскольку они следуют классической структуре ООП.

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

Функциональные компоненты были первоначально созданы для реализации простых компонентов без состояния. Однако с введением хуков в версии 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. Это позволяет отображать только те элементы, которые сейчас нужны, что облегчает поддержку кода и улучшает производительность.

Рекомендуем публикации по теме