Lifecycle Hooks
Il ciclo di vita di un componente
Ogni componente Vue attraversa una serie di fasi dalla creazione alla distruzione. Gli hook del ciclo di vita permettono di eseguire codice in momenti specifici di questo processo.
Diagramma del ciclo di vita
Il flusso completo del ciclo di vita di un componente Vue 3 e il seguente.
- Setup - La funzione setup viene eseguita (Composition API)
- beforeCreate / created - Inizializzazione del componente (solo Options API)
- onBeforeMount - Prima che il template venga montato nel DOM
- onMounted - Il componente e montato nel DOM
- onBeforeUpdate - Prima che il DOM venga aggiornato dopo un cambio dati
- onUpdated - Il DOM e stato aggiornato
- onBeforeUnmount - Prima che il componente venga rimosso dal DOM
- onUnmounted - Il componente e stato rimosso dal DOM
onMounted
Eseguito dopo che il componente e stato montato nel DOM. E l’hook piu utilizzato per operazioni che richiedono l’accesso al DOM o per caricare dati iniziali.
<script setup>
import { ref, onMounted } from 'vue'
const dati = ref<any[]>([])
const isCaricamento = ref(true)
const altezzaElemento = ref(0)
const contenitore = ref<HTMLElement | null>(null)
onMounted(async () => {
// Accesso al DOM (l'elemento esiste ora)
if (contenitore.value) {
altezzaElemento.value = contenitore.value.offsetHeight
}
// Caricamento dati iniziale
try {
const risposta = await fetch('/api/dati')
dati.value = await risposta.json()
} catch (errore) {
console.error('Errore caricamento:', errore)
} finally {
isCaricamento.value = false
}
})
// Puoi registrare piu onMounted nello stesso componente
onMounted(() => {
console.log('Secondo onMounted - entrambi verranno eseguiti')
})
</script>
<template>
<div ref="contenitore">
<p v-if="isCaricamento">Caricamento...</p>
<ul v-else>
<li v-for="item in dati" :key="item.id">{{ item.nome }}</li>
</ul>
</div>
</template>
Uso con librerie esterne
<script setup>
import { ref, onMounted, onUnmounted } from 'vue'
const canvasRef = ref<HTMLCanvasElement | null>(null)
let grafico: any = null
onMounted(() => {
if (canvasRef.value) {
// Inizializza una libreria che richiede un elemento DOM
grafico = new Chart(canvasRef.value, {
type: 'bar',
data: {
labels: ['Gen', 'Feb', 'Mar', 'Apr'],
datasets: [{
label: 'Vendite',
data: [12, 19, 3, 5]
}]
}
})
}
})
onUnmounted(() => {
// Pulisci la libreria esterna
if (grafico) {
grafico.destroy()
}
})
</script>
<template>
<canvas ref="canvasRef"></canvas>
</template>
onUpdated
Eseguito dopo che il DOM del componente e stato aggiornato a seguito di un cambio di stato reattivo.
<script setup>
import { ref, onUpdated } from 'vue'
const messaggi = ref<string[]>([])
const contenitoreLista = ref<HTMLElement | null>(null)
// Scroll automatico verso il basso quando arrivano nuovi messaggi
onUpdated(() => {
if (contenitoreLista.value) {
contenitoreLista.value.scrollTop = contenitoreLista.value.scrollHeight
}
})
function aggiungiMessaggio() {
messaggi.value.push(`Messaggio ${messaggi.value.length + 1}`)
}
</script>
<template>
<div ref="contenitoreLista" class="lista-messaggi">
<p v-for="(msg, i) in messaggi" :key="i">{{ msg }}</p>
</div>
<button @click="aggiungiMessaggio">Aggiungi messaggio</button>
</template>
Attenzione con onUpdated
Non modificare lo stato reattivo all’interno di onUpdated senza una condizione di uscita, altrimenti crei un loop infinito.
<script setup>
import { ref, onUpdated } from 'vue'
const contatore = ref(0)
onUpdated(() => {
// SBAGLIATO: crea un loop infinito
// contatore.value++
// CORRETTO: con condizione di uscita
if (contatore.value < 10) {
console.log('Aggiornato, contatore:', contatore.value)
}
})
</script>
onUnmounted
Eseguito dopo che il componente e stato rimosso dal DOM. Ideale per il cleanup di risorse.
<script setup>
import { ref, onMounted, onUnmounted } from 'vue'
const posizioneMouse = ref({ x: 0, y: 0 })
let intervalloId: ReturnType<typeof setInterval> | null = null
// Gestore evento
function aggiornaPosizione(evento: MouseEvent) {
posizioneMouse.value = { x: evento.clientX, y: evento.clientY }
}
onMounted(() => {
// Aggiungi event listener
window.addEventListener('mousemove', aggiornaPosizione)
// Avvia un intervallo
intervalloId = setInterval(() => {
console.log('Tick')
}, 1000)
})
onUnmounted(() => {
// Rimuovi event listener
window.removeEventListener('mousemove', aggiornaPosizione)
// Ferma l'intervallo
if (intervalloId) {
clearInterval(intervalloId)
}
console.log('Componente smontato, risorse pulite')
})
</script>
<template>
<p>Mouse: {{ posizioneMouse.x }}, {{ posizioneMouse.y }}</p>
</template>
Pattern comune: WebSocket cleanup
<script setup>
import { ref, onMounted, onUnmounted } from 'vue'
const messaggi = ref<string[]>([])
let ws: WebSocket | null = null
onMounted(() => {
ws = new WebSocket('wss://esempio.com/chat')
ws.onmessage = (evento) => {
messaggi.value.push(evento.data)
}
ws.onerror = (errore) => {
console.error('Errore WebSocket:', errore)
}
})
onUnmounted(() => {
if (ws) {
ws.close()
ws = null
}
})
</script>
onBeforeMount
Eseguito prima che il componente venga montato nel DOM. Il template e compilato ma non ancora inserito nel documento.
<script setup>
import { onBeforeMount } from 'vue'
onBeforeMount(() => {
console.log('Il componente sta per essere montato')
// Il DOM non e ancora disponibile qui
// Utile per logica di inizializzazione che non richiede il DOM
})
</script>
onBeforeUpdate
Eseguito prima che il DOM venga aggiornato. Utile per accedere allo stato del DOM prima delle modifiche.
<script setup>
import { ref, onBeforeUpdate, onUpdated } from 'vue'
const lista = ref(['A', 'B', 'C'])
let altezzaPrecedente = 0
onBeforeUpdate(() => {
// Salva lo stato del DOM prima dell'aggiornamento
const elemento = document.getElementById('mia-lista')
if (elemento) {
altezzaPrecedente = elemento.scrollHeight
}
})
onUpdated(() => {
// Confronta con lo stato dopo l'aggiornamento
const elemento = document.getElementById('mia-lista')
if (elemento) {
const nuovaAltezza = elemento.scrollHeight
if (nuovaAltezza !== altezzaPrecedente) {
console.log(`Altezza lista cambiata: ${altezzaPrecedente} => ${nuovaAltezza}`)
}
}
})
</script>
onBeforeUnmount
Eseguito prima che il componente venga rimosso dal DOM. Il componente e ancora completamente funzionale.
<script setup>
import { onBeforeUnmount } from 'vue'
onBeforeUnmount(() => {
console.log('Il componente sta per essere smontato')
// Utile per salvare lo stato prima della rimozione
salvaBozza()
})
function salvaBozza() {
// Salva i dati correnti
localStorage.setItem('bozza', JSON.stringify({ /* dati */ }))
}
</script>
Hook per il debugging
Vue offre hook speciali utili durante lo sviluppo.
onRenderTracked e onRenderTriggered
<script setup>
import { ref, onRenderTracked, onRenderTriggered } from 'vue'
const contatore = ref(0)
// Chiamato quando una dipendenza reattiva viene tracciata durante il render
onRenderTracked((evento) => {
// Solo in development
console.log('Dipendenza tracciata:', evento)
// { effect, target, type, key }
})
// Chiamato quando una dipendenza reattiva causa un re-render
onRenderTriggered((evento) => {
console.log('Re-render causato da:', evento)
// Utile per capire COSA causa un re-render
})
</script>
<template>
<button @click="contatore++">{{ contatore }}</button>
</template>
onErrorCaptured
Cattura errori provenienti dai componenti discendenti.
<script setup>
import { ref, onErrorCaptured } from 'vue'
const errore = ref<string | null>(null)
onErrorCaptured((err, istanza, info) => {
// err: l'errore catturato
// istanza: il componente che ha generato l'errore
// info: stringa con informazioni sulla fonte dell'errore
errore.value = `Errore: ${err.message} (${info})`
console.error('Errore catturato:', err, info)
// Restituisci false per impedire la propagazione verso l'alto
return false
})
</script>
<template>
<div v-if="errore" class="errore">
{{ errore }}
</div>
<slot v-else />
</template>
Componente ErrorBoundary
<!-- ErrorBoundary.vue -->
<script setup lang="ts">
import { ref, onErrorCaptured } from 'vue'
const props = defineProps<{
fallback?: string
}>()
const errore = ref<Error | null>(null)
onErrorCaptured((err) => {
errore.value = err as Error
return false
})
function riprova() {
errore.value = null
}
</script>
<template>
<div v-if="errore" class="error-boundary">
<h3>{{ fallback || 'Si e verificato un errore' }}</h3>
<p>{{ errore.message }}</p>
<button @click="riprova">Riprova</button>
</div>
<slot v-else />
</template>
<!-- Utilizzo -->
<template>
<ErrorBoundary fallback="Errore nel caricamento del profilo">
<ProfiloUtente :id="utenteId" />
</ErrorBoundary>
</template>
onActivated e onDeactivated
Usati con <KeepAlive> per componenti che vengono mantenuti in cache.
<script setup>
import { ref, onActivated, onDeactivated } from 'vue'
const ultimoAccesso = ref<Date | null>(null)
// Chiamato quando il componente viene riattivato dalla cache KeepAlive
onActivated(() => {
ultimoAccesso.value = new Date()
console.log('Componente riattivato')
// Aggiorna dati se necessario
})
// Chiamato quando il componente viene messo in cache da KeepAlive
onDeactivated(() => {
console.log('Componente disattivato (in cache)')
// Ferma operazioni non necessarie durante la cache
})
</script>
Riepilogo degli hook
| Hook | Quando si attiva | Uso tipico |
|---|---|---|
onBeforeMount |
Prima del mount DOM | Inizializzazione senza DOM |
onMounted |
Dopo il mount DOM | Fetch dati, accesso DOM, librerie esterne |
onBeforeUpdate |
Prima dell’aggiornamento DOM | Cattura stato DOM pre-update |
onUpdated |
Dopo l’aggiornamento DOM | Operazioni DOM post-update |
onBeforeUnmount |
Prima della rimozione | Salvataggio stato |
onUnmounted |
Dopo la rimozione | Cleanup risorse |
onErrorCaptured |
Errore in componente figlio | Error boundary |
onActivated |
Riattivazione KeepAlive | Refresh dati |
onDeactivated |
Disattivazione KeepAlive | Pausa operazioni |
Best practice
- Pulisci sempre le risorse in
onUnmounted: Event listener, timer, connessioni WebSocket - Non accedere al DOM in
onBeforeMount: L’elemento non esiste ancora - Evita modifiche di stato in
onUpdated: Rischio di loop infiniti - Usa
onErrorCapturedper error boundaries: Gestisci errori dei componenti figli - Registra gli hook in modo sincrono nel setup: Non usarli in callback asincroni o setTimeout
- Usa
onMountedper le chiamate API iniziali: E il posto standard per il fetch dei dati