Жизненный цикл компонента React состоит из трёх фаз: монтирование (компонент впервые появляется на экране), обновление (он рендерится снова, потому что изменились его состояние, пропсы или контекст) и размонтирование (он удаляется). В функциональных компонентах на эти фазы реагируют через useEffect: эффект с [] выполняется после монтирования, эффект с зависимостями выполняется после обновлений, в которых они изменились, а функция, которую возвращает эффект, выполняется при размонтировании.
Консоль под превью показывает каждую фазу, когда она происходит.
Консоль начинается с render, count = 0 и mounted. Каждый клик по Count выводит рендер и обновление. Unmount выводит unmounted; Mount again начинает сначала с count = 0, потому что состояние было выброшено вместе с компонентом.
Монтирование
Компонент монтируется, когда React впервые рендерит его в определённом месте дерева. React вызывает функцию, создаёт узлы DOM из возвращённого JSX, вставляет их, даёт браузеру отрисовать страницу, а затем выполняет эффекты компонента.
useEffect(fn, []) это место, где запускают то, что должно существовать, пока компонент на экране: подписку, таймер, соединение, первый запрос данных. Он выполняется после отрисовки, поэтому никогда не задерживает первый кадр.
Обновление
Компонент обновляется (рендерится снова), когда:
- меняется его собственное состояние,
- снова рендерится его родитель (по умолчанию каждый дочерний компонент тоже рендерится, изменились ли его пропсы или нет),
- меняется контекст, который он читает.
После каждого обновления React выполняет эффекты, зависимости которых изменились. Перед повторным запуском эффекта он выполняет предыдущую очистку этого эффекта, поэтому старая подписка закрывается до того, как открывается новая. Страница о рендеринге подробнее разбирает, что вызывает рендер.
Размонтирование
Компонент размонтируется, когда родитель перестаёт его рендерить (как делает {show && <Counter />} выше), когда меняется его key или когда размонтируется сам корень. React удаляет его узлы DOM, выполняет очистку каждого эффекта и выбрасывает его состояние. Если компонент позже появится снова, это будет совершенно новый экземпляр.
Родители и дети
React рендерит сверху вниз, а эффекты выполняет снизу вверх. Когда родитель и дочерний компонент монтируются вместе, эффекты дочернего выполняются первыми, поэтому эффект родителя может полагаться на то, что его дети полностью настроены.
Монтирование выводит render Parent, render Child, Child effect, Parent effect. Нажмите Remove, и очистки выполнятся в обратном порядке: Parent cleanup, затем Child cleanup.
Фазы рендера и фиксации
Каждое обновление проходит две фазы:
- Фаза рендера. React вызывает ваши компоненты, чтобы вычислить новый JSX, и сравнивает его с предыдущим результатом. На экране пока ничего не меняется. React может приостановить эту работу, выполнить её дважды в StrictMode или выбросить, поэтому рендеринг должен быть чистым: никаких запросов, подписок, изменений внешних переменных.
- Фаза фиксации (commit). React применяет различия к DOM, присоединяет рефы и выполняет колбэки
useLayoutEffect, и всё это до отрисовки браузером. КолбэкиuseEffectвыполняются после этого, как правило, когда браузер уже отрисовал страницу. Когда обновление вызвано кликом или другим дискретным вводом, React вместо этого выполняет их сразу после layout-эффектов, чтобы результат был готов до следующего события.
Каждый клик выводит три строки в одном и том же порядке. У этих эффектов нет массива зависимостей, поэтому они выполняются после каждого рендера. Добавьте [] вторым аргументом к обоим, и они будут выполняться только после первого.
Методы жизненного цикла классов и их эквиваленты в хуках
Классовые компоненты расписывают жизненный цикл как именованные методы. Вы встретите их в старом коде и в границах ошибок, которым по-прежнему нужен класс.
| Метод класса | Когда выполняется | Эквивалент в функциональном компоненте |
|---|---|---|
constructor | один раз, до первого рендера | useState(initial), useRef |
static getDerivedStateFromProps | перед каждым рендером | вычислить значение во время рендера или сбросить через key |
render | каждый рендер | тело функции |
componentDidMount | после того как первый рендер оказался в DOM | useEffect(fn, []) |
shouldComponentUpdate | перед обновлением, может его пропустить | React.memo (он отвечает на обратный вопрос: «равны ли пропсы») |
getSnapshotBeforeUpdate | прямо перед изменением DOM | прямого хука нет; ближе всего useLayoutEffect с ref |
componentDidUpdate | после каждого обновления | useEffect(fn, [deps]) (выполняется и после монтирования) |
componentWillUnmount | перед удалением | очистка, возвращаемая из useEffect |
getDerivedStateFromError, componentDidCatch | когда дочерний компонент выбрасывает ошибку | нет: напишите класс границы ошибок |
Вот класс, который выводит каждый метод по мере выполнения:
Монтирование выводит constructor, getDerivedStateFromProps, render, componentDidMount. Клик выводит getDerivedStateFromProps, shouldComponentUpdate, render, getSnapshotBeforeUpdate, componentDidUpdate. Сделайте так, чтобы shouldComponentUpdate возвращал false, и клики перестанут обновлять кнопку: состояние меняется, но React пропускает рендер. Страница о классовых компонентах показывает, как превратить такой класс в функцию.
Думайте о синхронизации, а не о жизненном цикле
Таблица хуков полезна для чтения старого кода, но может подтолкнуть к эффектам, которые копируют привычки классов, например к одному большому эффекту «did mount» и одному большому «did update». Эффект лучше читать как «держи эту внешнюю вещь в синхронизации с этими значениями»: подключись к roomId, а когда roomId изменится, отключись и подключись снова. Пишите по одному эффекту на каждую вещь, которую синхронизируете, перечисляйте используемые значения как зависимости и позвольте React решать, когда его запускать.
Часто задаваемые вопросы
Какие фазы у жизненного цикла компонента React?
Монтирование (React создаёт компонент и выводит его результат на экран), обновление (он рендерится снова, потому что изменились его состояние, пропсы или контекст) и размонтирование (он удаляется и выполняет очистку).
Какой хук эквивалентен componentDidMount?
useEffect(() => { ... }, []). Пустой массив зависимостей заставляет его выполниться один раз, после того как первый рендер оказался на экране. Функция, которую он возвращает, эквивалентна componentWillUnmount.
Какой хук эквивалентен componentDidUpdate?
useEffect с нужными вам значениями в массиве зависимостей, например [userId]. В отличие от componentDidUpdate, он выполняется и после первого рендера, поэтому покрывает монтирование и обновление вместе.
В каком порядке выполняются эффекты родителя и дочернего компонента?
React рендерит сверху вниз, а эффекты выполняет снизу вверх: эффект дочернего компонента выполняется раньше эффекта родителя. При размонтировании очистка родителя выполняется раньше очистки дочернего.
Что такое фазы рендера и фиксации?
В фазе рендера React вызывает ваши компоненты, чтобы понять, как должен выглядеть экран; он может приостановить или выбросить эту работу. В фазе фиксации (commit) он применяет изменения к DOM и выполняет layout-эффекты; обычные эффекты выполняются после этого, как правило, когда браузер уже отрисовал страницу.