Расскажи про жизненный цикл функциональных компонентов React
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Жизненный цикл функциональных компонентов React: от хуков до обновлений
В современном React с использованием функциональных компонентов концепция "жизненного цикла" перестала быть строго последовательной, как в классовых компонентах (mount → update → unmount). Вместо этого мы управляем состоянием и побочными эффектами через хуки (Hooks), которые позволяют "подключаться" к различным фазам существования компонента. Основные этапы можно описать следующим образом.
Фазы существования функционального компонента
- Монтирование (Mounting) – компонент впервые добавляется в DOM.
- Обновление (Updating) – компонент перерисовывается из-за изменения пропсов, состояния или контекста.
- Размонтирование (Unmounting) – компонент удаляется из DOM.
Ключевые хуки для управления жизненным циклом
useState и useReducer отвечают за состояние, но сами не являются "жизненным циклом". Они просто хранят данные между рендерами. Основную работу по реакции на фазы выполняют useEffect и useLayoutEffect.
useEffect: основной инструмент для побочных эффектов
Хук useEffect позволяет выполнять код после того, как компонент отрендерился и изменения были зафиксированы в DOM. Его поведение зависит от передаваемого массива зависимостей.
import { useEffect } from 'react';
function MyComponent({ userId }) {
useEffect(() => {
// Этот код выполнится после каждого рендера, если массив зависимостей пуст
console.log('Компонент отрендерился в DOM');
});
useEffect(() => {
// Этот код выполнится только при монтировании и размонтировании
console.log('Эффект на монтирование (аналог componentDidMount)');
return () => {
console.log('Эффект на размонтирование (аналог componentWillUnmount)');
};
}, []); // Пустой массив зависимостей
useEffect(() => {
// Этот код выполнится при монтировании и при каждом изменении userId
fetchUserData(userId);
return () => {
// Очистка предыдущего эффекта перед новым выполнением или размонтированием
abortFetch();
};
}, [userId]); // Массив с зависимостью userId
}
- Пустой массив зависимостей
[]: эффект выполняется только один раз после монтирования, а функция очистки (return) – при размонтировании. - Массив с зависимостью
[userId]: эффект выполняется после монтирования и после каждого обновления, еслиuserIdизменился. Функция очистки выполняется перед следующим выполнением этого эффекта или перед размонтировании. - Отсутствие массива: эффект выполняется после каждого рендера (монтирования и всех обновлений). Это может быть неэффективно.
useLayoutEffect: эффект перед отображением пользователю
Хук useLayoutEffect имеет одинаковую сигнатуру с useEffect, но выполняется синхронно сразу после вычисления DOM-обновлений, но перед тем, как браузер отобразит их пользователю. Это полезно для измерений DOM или мутаций, которые должны быть видимы пользователю сразу.
import { useLayoutEffect, useRef } from 'react';
function Tooltip() {
const ref = useRef();
useLayoutEffect(() => {
// Измеряем размеры элемента перед отображением
const { height } = ref.current.getBoundingClientRect();
// Немедленно применяем стиль, чтобы пользователь не увидел промежуточное состояние
ref.current.style.top = `calc(100% - ${height}px)`;
}, []);
return <div ref={ref}>Текст подсказки</div>;
}
Ключевое отличие: useLayoutEffect блокирует отображение браузера, поэтому используйте его осторожно, чтобы избежать проблем с производительностью. В большинстве случаев для данных, запросов, подписок используйте useEffect.
Полный цикл на примере компонента
Рассмотрим компонент, который получает данные при монтировании и отслеживает изменения пропса.
import { useState, useEffect } from 'react';
function UserProfile({ userId }) {
// 1. Инициализация состояния (происходит при каждом рендере)
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
// 2. Рендер (вычисление UI)
// Здесь React вычисляет, что нужно вернуть. DOM еще не обновлен.
// 3. После рендера и обновления DOM запускаются эффекты
useEffect(() => {
let isCurrent = true;
setLoading(true);
fetch(`/api/users/${userId}`)
.then(res => res.json())
.then(data => {
if (isCurrent) {
setUser(data);
setLoading(false);
}
});
// Функция очистки: выполнится перед следующим вызовом этого эффекта или при размонтировании
return () => {
isCurrent = false;
console.log(`Очистка эффекта для userId: ${userId}`);
};
}, [userId]); // Эффект зависит от userId
// 4. Размонтирование: если компонент удаляется из дерева,
// функция очистки в useEffect выше будет выполнена.
if (loading) return <div>Загрузка...</div>;
return <div>{user.name}</div>;
}
Другие важные хуки, влияющие на цикл
- useMemo и useCallback: используются для оптимизации, предотвращая повторные вычисления или создания функций при обновлениях, если зависимости не изменились. Они не вызывают эффекты сами, но влияют на то, что передается дочерним компонентам.
- useRef: создает объект-контейнер, который сохраняет свое значение между рендерами. Изменение
ref.currentне вызывает повторный рендер. - Контекст (useContext): позволяет компоненту реагировать на изменения контекста, что также запускает этап обновления.
Отличия от классовых компонентов
В классовых компонентах были отдельные методы (componentDidMount, componentDidUpdate, componentWillUnmount). В функциональных компонентах один хук useEffect объединяет все эти сценарии, а логика разделяется через массив зависимостей. Это делает код более линейным и позволяет группировать связанную логику (например, подписку и очистку) вместе, вместо того чтобы разбрасывать ее по разным методам класса.
Итог: Жизненный цикл функционального компонента React управляется через комбинацию хуков. useEffect — центральный инструмент для выполнения кода в ответ на монтирование, обновление и размонтирование. useLayoutEffect используется для синхронных манипуляций с DOM перед отображением. Правильное указание массива зависимостей в useEffect критически важно для корректного поведения и предотвращения бесконечных циклов или утечек памяти.