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 и реактивные эффекты оба завязаны на текущий экземпляр компонента и порядок обновлений