11. Lifecycle Hooks
⏰ Lifecycle Hooks в Vue 3
Заголовок раздела «⏰ Lifecycle Hooks в Vue 3»Каждый компонент Vue проходит через несколько этапов жизни: создание, монтирование в DOM, обновление при изменении данных и уничтожение. На каждом этапе можно “подцепиться” к нужному моменту с помощью lifecycle hooks. Это как подписка на события жизни компонента! 🎭
Диаграмма жизненного цикла
Заголовок раздела «Диаграмма жизненного цикла» Создание экземпляра │ setup() ← Composition API запускается здесь │ onBeforeMount() │ Создание VDOM → монтирование в реальный DOM │ onMounted() ✅ ← DOM доступен! │ ┌──────┴──────┐ │ Реактивные │ │ данные │ │ изменились │ └──────┬──────┘ │ onBeforeUpdate() │ Патч DOM (только изменения) │ onUpdated() │ <KeepAlive> deactivated/activated │ Компонент удаляется │ onBeforeUnmount() │ Очистка слушателей, таймеров, подписок │ onUnmounted() ✅ ← DOM удалёнonBeforeMount и onMounted
Заголовок раздела «onBeforeMount и onMounted»onBeforeMount — компонент готов к рендеру, но ещё не вставлен в DOM. onMounted — DOM уже есть, можно работать с реальными элементами:
<script setup lang="ts">import { ref, onBeforeMount, onMounted} from 'vue'
const title = ref('Загрузка...')const canvasRef = ref<HTMLCanvasElement | null>(null)const users = ref<string[]>([])
onBeforeMount(() => { // DOM ещё нет! el === null console.log('onBeforeMount: готовимся к рендеру') // Можно подготовить данные title.value = 'Компонент инициализирован'})
onMounted(async () => { // DOM уже есть! Можно работать с canvasRef.value console.log('onMounted: DOM доступен 🎉')
// 1. Работа с DOM-элементами if (canvasRef.value) { const ctx = canvasRef.value.getContext('2d') ctx?.fillRect(0, 0, 100, 100) }
// 2. Запросы к API const response = await fetch('/api/users') users.value = await response.json()
// 3. Инициализация сторонних библиотек // new Chart(canvasRef.value, {...})})</script>
<template> <h1>{{ title }}</h1> <canvas ref="canvasRef" width="300" height="200" /> <ul> <li v-for="user in users" :key="user">{{ user }}</li> </ul></template>💡 Важно:
onMounted— самый частый хук. Используй его для запросов к API, инициализации библиотек (Chart.js, Leaflet, GSAP) и работы с DOM.
onBeforeUpdate и onUpdated
Заголовок раздела «onBeforeUpdate и onUpdated»Эти хуки срабатывают при каждом обновлении компонента из-за изменений реактивных данных:
<script setup lang="ts">import { ref, onBeforeUpdate, onUpdated } from 'vue'
const count = ref(0)const items = ref(['Яблоко', 'Банан'])const listRef = ref<HTMLUListElement | null>(null)
onBeforeUpdate(() => { // DOM ещё старый! console.log('onBeforeUpdate: сейчас DOM обновится') // Например, можно сохранить позицию скролла перед обновлением const scrollPos = listRef.value?.scrollTop console.log('Текущий скролл:', scrollPos)})
onUpdated(() => { // DOM уже обновлён! console.log('onUpdated: DOM синхронизирован ✅')
// Пример: автоскролл к последнему элементу if (listRef.value) { listRef.value.scrollTop = listRef.value.scrollHeight }})</script>
<template> <button @click="items.push('Элемент ' + (items.length + 1))"> Добавить </button> <ul ref="listRef" style="max-height: 100px; overflow-y: auto"> <li v-for="item in items" :key="item">{{ item }}</li> </ul></template>⚠️ Осторожно: Не изменяй реактивные данные в
onUpdated— это вызовет бесконечный цикл обновлений!
onBeforeUnmount и onUnmounted
Заголовок раздела «onBeforeUnmount и onUnmounted»Это самые важные хуки для очистки ресурсов. Без очистки — утечки памяти 🕳️:
<script setup lang="ts">import { ref, onMounted, onBeforeUnmount, onUnmounted } from 'vue'
const position = ref({ x: 0, y: 0 })let animationFrameId: numberlet intervalId: ReturnType<typeof setInterval>let socket: WebSocket
// Мышьfunction handleMouseMove(e: MouseEvent) { position.value = { x: e.clientX, y: e.clientY }}
onMounted(() => { // Подписки, которые нужно очистить window.addEventListener('mousemove', handleMouseMove)
intervalId = setInterval(() => { console.log('Тик...') }, 1000)
socket = new WebSocket('wss://example.com')
// Анимационный цикл const animate = () => { // ... логика animationFrameId = requestAnimationFrame(animate) } animationFrameId = requestAnimationFrame(animate)})
onBeforeUnmount(() => { // Вызывается ДО удаления из DOM // Ещё можно работать с DOM-элементами console.log('onBeforeUnmount: компонент скоро исчезнет')})
onUnmounted(() => { // Вызывается ПОСЛЕ удаления из DOM // Обязательная очистка! window.removeEventListener('mousemove', handleMouseMove) clearInterval(intervalId) cancelAnimationFrame(animationFrameId) socket.close() console.log('onUnmounted: всё почищено ✅')})</script>onErrorCaptured — перехват ошибок
Заголовок раздела «onErrorCaptured — перехват ошибок»Хук для обработки ошибок дочерних компонентов. Работает как Error Boundary в React:
<script setup lang="ts">import { ref, onErrorCaptured } from 'vue'
interface ErrorInfo { message: string component: string type: string}
const error = ref<ErrorInfo | null>(null)
onErrorCaptured((err: Error, instance, info) => { console.error('Перехвачена ошибка:', err)
error.value = { message: err.message, component: instance?.$options.name || 'Unknown', type: info, // 'render', 'setup', 'watch', etc. }
// Вернуть false — ошибка не распространяется выше // Вернуть true (или ничего) — ошибка продолжает всплывать return false})</script>
<template> <div> <div v-if="error" class="error-boundary"> <h2>⚠️ Что-то пошло не так</h2> <p>{{ error.message }}</p> <button @click="error = null">Попробовать снова</button> </div> <slot v-else /> </div></template>onActivated и onDeactivated — KeepAlive
Заголовок раздела «onActivated и onDeactivated — KeepAlive»Когда компонент обёрнут в <KeepAlive>, он не уничтожается при скрытии — а деактивируется. Для таких компонентов есть специальные хуки:
<!-- Родительский компонент --><template> <KeepAlive> <component :is="currentComponent" /> </KeepAlive></template><!-- Компонент внутри KeepAlive --><script setup lang="ts">import { ref, onActivated, onDeactivated, onMounted, onUnmounted } from 'vue'
const lastActivated = ref<Date | null>(null)let pollingInterval: ReturnType<typeof setInterval>
// Вызывается ОДИН РАЗ при первом монтированииonMounted(() => { console.log('onMounted: компонент создан')})
// Вызывается каждый раз при ПОКАЗЕ компонентаonActivated(() => { console.log('onActivated: компонент снова активен') lastActivated.value = new Date()
// Возобновляем polling данных pollingInterval = setInterval(fetchData, 5000)})
// Вызывается при СКРЫТИИ (но не удалении!)onDeactivated(() => { console.log('onDeactivated: компонент скрыт') clearInterval(pollingInterval)})
// Вызывается при полном УДАЛЕНИИ (если KeepAlive убран)onUnmounted(() => { console.log('onUnmounted: компонент удалён совсем')})
async function fetchData() { // Обновление данных...}</script>onServerPrefetch — SSR
Заголовок раздела «onServerPrefetch — SSR»Для серверного рендеринга (Nuxt и т.п.) есть специальный хук для предзагрузки данных:
<script setup lang="ts">import { ref, onServerPrefetch } from 'vue'import { useAsyncData } from '#imports' // Nuxt
const posts = ref([])
// Выполняется только на сервере перед рендеромonServerPrefetch(async () => { posts.value = await fetch('https://api.example.com/posts') .then(r => r.json())
// Данные будут вставлены в HTML до отправки клиенту})</script>Хуки в Composition API vs Options API
Заголовок раздела «Хуки в Composition API vs Options API»<!-- Options API (старый стиль) --><script>export default { // НЕТ beforeCreate/created — setup() их заменяет beforeCreate() {}, created() {}, // ← в Composition API = просто код в setup()
beforeMount() {}, mounted() {},
beforeUpdate() {}, updated() {},
beforeUnmount() {}, unmounted() {},
errorCaptured() {}, activated() {}, deactivated() {}, serverPrefetch() {},}</script><!-- Composition API (новый стиль) --><script setup lang="ts">import { onBeforeMount, // beforeMount onMounted, // mounted onBeforeUpdate, // beforeUpdate onUpdated, // updated onBeforeUnmount, // beforeUnmount onUnmounted, // unmounted onErrorCaptured, // errorCaptured onActivated, // activated onDeactivated, // deactivated onServerPrefetch, // serverPrefetch} from 'vue'
// beforeCreate и created — не нужны!// Любой код в setup() = created()const msg = 'Это выполняется как created'</script>Несколько обработчиков одного хука
Заголовок раздела «Несколько обработчиков одного хука»В Composition API можно вызвать один хук несколько раз! Все обработчики выполнятся по порядку:
<script setup lang="ts">import { onMounted } from 'vue'
// Это удобно, когда логика в разных composablesonMounted(() => { console.log('1. Первый обработчик')})
onMounted(() => { console.log('2. Второй обработчик')})
// Из composable:// useFetchData() внутри тоже вызывает onMounted// и это работает!Реальный пример: компонент с полным циклом
Заголовок раздела «Реальный пример: компонент с полным циклом»<script setup lang="ts">import { ref, onMounted, onBeforeUnmount, onActivated, onDeactivated } from 'vue'
interface Post { id: number title: string body: string}
const posts = ref<Post[]>([])const loading = ref(false)const error = ref<string | null>(null)let abortController: AbortController
async function fetchPosts() { abortController = new AbortController() loading.value = true error.value = null
try { const response = await fetch( 'https://jsonplaceholder.typicode.com/posts?_limit=5', { signal: abortController.signal } ) posts.value = await response.json() } catch (e) { if (e instanceof Error && e.name !== 'AbortError') { error.value = e.message } } finally { loading.value = false }}
onMounted(() => { fetchPosts()})
onActivated(() => { // Обновляем данные при повторном показе fetchPosts()})
onBeforeUnmount(() => { // Отменяем незавершённые запросы abortController?.abort()})</script>
<template> <div> <div v-if="loading">⏳ Загрузка...</div> <div v-else-if="error">❌ {{ error }}</div> <ul v-else> <li v-for="post in posts" :key="post.id"> {{ post.title }} </li> </ul> </div></template>Шпаргалка хуков
Заголовок раздела «Шпаргалка хуков»| Хук | Когда | Доступ к DOM | Типичное использование |
|---|---|---|---|
onBeforeMount | До вставки в DOM | ❌ | Подготовка данных |
onMounted | После вставки в DOM | ✅ | API запросы, DOM-библиотеки |
onBeforeUpdate | До патча DOM | ✅ (старый) | Сохранение состояния |
onUpdated | После патча DOM | ✅ (новый) | Реакция на DOM-изменения |
onBeforeUnmount | До удаления | ✅ | Последние действия |
onUnmounted | После удаления | ❌ | Очистка ресурсов |
onErrorCaptured | При ошибке дочернего | — | Error boundary |
onActivated | KeepAlive: показ | ✅ | Возобновление polling |
onDeactivated | KeepAlive: скрытие | ✅ | Пауза polling |
onServerPrefetch | SSR до рендера | — | Предзагрузка данных |