Vue

Хуки жизненного цикла

Компонент с интерактивной картой Leaflet не может создать карту, пока в DOM нет элемента-контейнера: библиотеке нужен реальный div с известными размерами. Если дёрнуть инициализацию слишком рано, карта отрисуется в нулевой области или упадёт. А когда пользователь уходит со страницы, забытый обработчик resize и таймер обновления тайлов продолжают висеть и утекать память. Жизненный цикл отвечает ровно на два вопроса: когда DOM уже готов и когда пора всё убрать за собой.

  • Инициализация сторонних библиотек (Leaflet, Chart.js, видеоплеер) в onMounted, когда контейнер уже в DOM
  • Подписка на глобальные события (resize, scroll, WebSocket) в onMounted и отписка в onUnmounted, чтобы не утекала память
  • Автофокус на поле формы после монтирования: до появления элемента в DOM фокус поставить некуда
  • Остановка таймеров и интервалов перед уничтожением компонента, иначе callback стреляет в несуществующий компонент

Предварительные знания

  • Однофайловые компоненты и блок script setup
  • Понимание, что Vue строит и обновляет DOM сам на основе шаблона
  • Базовое знание ref для доступа к DOM-элементу

Когда выполняется setup

Тело script setup - это и есть функция setup, и она выполняется один раз при создании экземпляра компонента, ещё до первого рендера. На этом этапе реактивное состояние объявляется, но DOM компонента не существует. Попытка обратиться к элементу через template ref прямо в теле setup даст null: элемент появится только после монтирования.

Хуки регистрируются синхронно в момент выполнения setup, потому что Vue в этот момент знает текущий активный экземпляр компонента. Если вызвать onMounted внутри setTimeout или после await, активного экземпляра уже нет и регистрация не сработает.

ЭтапСостояние DOM компонентаЧто делают
setup (тело script setup)НетОбъявляют ref, computed, регистрируют хуки
onBeforeMountЕщё не вставленПоследние приготовления перед первым рендером
onMountedГотов и в документеДоступ к элементам, init сторонних библиотек
onUnmountedУже удалёнОчистка: подписки, таймеры, инстансы

Почему обращение к template ref прямо в теле script setup возвращает null?

onBeforeMount и onMounted

onBeforeMount вызывается после setup, но до того, как Vue вставит сгенерированный DOM в документ. onMounted срабатывает сразу после вставки: к этому моменту все элементы шаблона существуют и доступны через template ref. Это главный хук для работы с реальным DOM и инициализации библиотек, которым нужен готовый контейнер.

onMounted гарантирует, что DOM самого компонента в документе, но не гарантирует, что дочерние компоненты завершили асинхронную работу. Для замеров после полной перерисовки используется nextTick внутри хука.

Сетевые запросы в Composition API чаще делают прямо в теле setup или в watchEffect, а не в onMounted: запрос не зависит от DOM. onMounted оставляют для того, что действительно требует готового элемента.

В каком хуке корректно инициализировать библиотеку графиков, которой нужен реальный canvas-элемент?

onUpdated: после перерисовки

onUpdated вызывается после того, как реактивное изменение состояния заставило Vue перерисовать DOM компонента. В этот момент DOM уже отражает новые данные. Хук полезен для редких задач, требующих доступа к обновлённому DOM, например прокрутить контейнер вниз после добавления нового сообщения в чат.

Изменять реактивное состояние внутри onUpdated опасно: новое изменение вызовет повторный рендер, тот снова вызовет onUpdated, и получится бесконечный цикл. Для реакции на конкретные данные предпочтителен watch, а не onUpdated.

В большинстве компонентов onUpdated не нужен вовсе. Если задача формулируется как 'сделать что-то при изменении значения X', точнее подходит watch по этому значению: он сработает только на нужное изменение, а не на любую перерисовку.

Почему изменение реактивного состояния внутри onUpdated считается опасным?

onBeforeUnmount и onUnmounted: уборка за собой

Когда компонент удаляется из дерева (смена маршрута, v-if стал false), Vue вызывает onBeforeUnmount, а затем onUnmounted. Всё, что было создано в onMounted и продолжает жить вне Vue, нужно убрать именно здесь: глобальные слушатели событий, таймеры, открытые сокеты, инстансы сторонних библиотек. Иначе callback продолжит срабатывать и обращаться к уже несуществующему компоненту - утечка памяти.

Парность - удобный ориентир: добавили слушатель в onMounted, сразу добавьте removeEventListener в onUnmounted. Когда такая логика повторяется, её выносят в composable, который сам подписывается и отписывается, как делает VueUse.

Что произойдёт, если addEventListener('resize', ...) добавлен в onMounted, но removeEventListener не вызван в onUnmounted?

Связь с другими темами

Жизненный цикл - основа для побочных эффектов. Дальше курс показывает их упаковку:

  • Composables — Хуки вызываются внутри composable, и вся логика подписки с очисткой переезжает в переиспользуемую функцию
  • Реактивность изнутри — Порядок выполнения хуков и реактивных эффектов завязан на один и тот же планировщик обновлений Vue

Итог

  • Код в script setup выполняется как setup - до первого рендера, поэтому DOM там ещё недоступен
  • onBeforeMount срабатывает до вставки в DOM, onMounted - сразу после: здесь работают с реальными элементами
  • onUpdated вызывается после того, как реактивное изменение перерисовало DOM компонента
  • onBeforeUnmount и onUnmounted нужны для очистки: снять подписки, остановить таймеры, разрушить сторонние инстансы
  • Хуки регистрируются синхронно в setup; вызов хука внутри callback или await после монтирования не сработает

Связанные уроки

  • vue-12-composables — Хуки жизненного цикла внутри composable позволяют инкапсулировать подписку и её очистку в одной функции
  • vue-18-reactivity-deep — onMounted и реактивные эффекты оба завязаны на текущий экземпляр компонента и порядок обновлений
Хуки жизненного цикла

0

1

Войти