Computed e Watch
computed()
Le proprieta calcolate (computed) derivano un valore da altri dati reattivi. Vue le memorizza in cache e le ricalcola solo quando le dipendenze cambiano.
<script setup>
import { ref, computed } from 'vue'
const nome = ref('Mario')
const cognome = ref('Rossi')
const articoli = ref([
{ nome: 'Laptop', prezzo: 999, quantita: 1 },
{ nome: 'Mouse', prezzo: 29, quantita: 2 },
{ nome: 'Tastiera', prezzo: 79, quantita: 1 }
])
// Computed semplice
const nomeCompleto = computed(() => `${nome.value} ${cognome.value}`)
// Computed con logica
const totaleCarrello = computed(() =>
articoli.value.reduce((totale, art) => totale + art.prezzo * art.quantita, 0)
)
const articoliOrdinati = computed(() =>
[...articoli.value].sort((a, b) => a.prezzo - b.prezzo)
)
const haArticoli = computed(() => articoli.value.length > 0)
</script>
<template>
<h2>{{ nomeCompleto }}</h2>
<p>Totale: {{ totaleCarrello.toFixed(2) }} EUR</p>
<p v-if="haArticoli">{{ articoli.length }} articoli nel carrello</p>
<ul>
<li v-for="art in articoliOrdinati" :key="art.nome">
{{ art.nome }} - {{ art.prezzo }} EUR x{{ art.quantita }}
</li>
</ul>
</template>
Computed vs metodi
La differenza fondamentale e la cache: una computed viene ricalcolata solo quando le sue dipendenze cambiano, mentre un metodo viene eseguito ad ogni render.
<script setup>
import { ref, computed } from 'vue'
const lista = ref([3, 1, 4, 1, 5, 9, 2, 6])
// COMPUTED: con cache, ricalcolata solo quando lista cambia
const listaOrdinata = computed(() => {
console.log('Computed ricalcolata')
return [...lista.value].sort((a, b) => a - b)
})
// METODO: eseguito ad ogni render
function getListaOrdinata() {
console.log('Metodo eseguito')
return [...lista.value].sort((a, b) => a - b)
}
</script>
<template>
<!-- La computed viene chiamata 3 volte ma calcolata 1 sola volta -->
<p>{{ listaOrdinata }}</p>
<p>{{ listaOrdinata }}</p>
<p>{{ listaOrdinata }}</p>
<!-- Il metodo viene eseguito 3 volte -->
<p>{{ getListaOrdinata() }}</p>
<p>{{ getListaOrdinata() }}</p>
<p>{{ getListaOrdinata() }}</p>
</template>
Computed writable
Di default le computed sono in sola lettura. Con getter e setter puoi creare computed scrivibili.
<script setup>
import { ref, computed } from 'vue'
const nome = ref('Mario')
const cognome = ref('Rossi')
// Computed con getter e setter
const nomeCompleto = computed({
get() {
return `${nome.value} ${cognome.value}`
},
set(nuovoValore: string) {
const parti = nuovoValore.split(' ')
nome.value = parti[0] || ''
cognome.value = parti.slice(1).join(' ') || ''
}
})
// Ora puoi assegnare un valore
// nomeCompleto.value = 'Luigi Verdi'
// => nome.value diventa 'Luigi', cognome.value diventa 'Verdi'
</script>
<template>
<input v-model="nomeCompleto" />
<p>Nome: {{ nome }}</p>
<p>Cognome: {{ cognome }}</p>
</template>
watch()
watch() osserva una o piu sorgenti reattive e esegue un callback quando cambiano. Utile per side effect come chiamate API, logging o manipolazione DOM.
Osservare un ref
<script setup>
import { ref, watch } from 'vue'
const ricerca = ref('')
const risultati = ref<string[]>([])
// Watch base su un ref
watch(ricerca, (nuovoValore, vecchioValore) => {
console.log(`Ricerca cambiata da "${vecchioValore}" a "${nuovoValore}"`)
})
// Watch con logica asincrona
watch(ricerca, async (query) => {
if (query.length < 3) {
risultati.value = []
return
}
const risposta = await fetch(`/api/ricerca?q=${query}`)
risultati.value = await risposta.json()
})
</script>
<template>
<input v-model="ricerca" placeholder="Cerca..." />
<ul>
<li v-for="r in risultati" :key="r">{{ r }}</li>
</ul>
</template>
Osservare un getter
Quando vuoi osservare una proprieta specifica di un oggetto reattivo, usa una funzione getter.
<script setup>
import { reactive, watch } from 'vue'
const stato = reactive({
filtro: {
categoria: 'tutti',
ordinamento: 'nome',
pagina: 1
},
dati: []
})
// Watch su una proprieta specifica (getter)
watch(
() => stato.filtro.categoria,
(nuovaCategoria) => {
console.log('Categoria cambiata:', nuovaCategoria)
stato.filtro.pagina = 1 // Reset pagina
}
)
// Watch su proprieta calcolata
watch(
() => stato.filtro.categoria + stato.filtro.ordinamento,
() => {
console.log('Filtro o ordinamento cambiati')
}
)
</script>
Osservare sorgenti multiple
<script setup>
import { ref, watch } from 'vue'
const latitudine = ref(41.9028)
const longitudine = ref(12.4964)
// Watch su array di sorgenti
watch(
[latitudine, longitudine],
([nuovaLat, nuovaLon], [vecchiaLat, vecchiaLon]) => {
console.log(`Posizione: ${vecchiaLat},${vecchiaLon} => ${nuovaLat},${nuovaLon}`)
aggiornaMappe(nuovaLat, nuovaLon)
}
)
function aggiornaMappe(lat: number, lon: number) {
// Aggiorna la mappa con le nuove coordinate
}
</script>
Opzione immediate
Con immediate: true, il callback viene eseguito immediatamente con il valore corrente.
<script setup>
import { ref, watch } from 'vue'
const idUtente = ref(1)
const utente = ref(null)
// Esegue subito alla creazione E ad ogni cambio successivo
watch(idUtente, async (id) => {
const risposta = await fetch(`/api/utenti/${id}`)
utente.value = await risposta.json()
}, { immediate: true })
</script>
Deep watch
Per osservare cambiamenti profondi in un oggetto, usa deep: true.
<script setup>
import { ref, watch } from 'vue'
const form = ref({
personali: {
nome: '',
cognome: '',
indirizzo: {
via: '',
citta: '',
cap: ''
}
},
preferenze: {
newsletter: false,
tema: 'chiaro'
}
})
// Deep watch: osserva qualsiasi cambiamento nel form
watch(form, (nuovoForm) => {
console.log('Form modificato:', JSON.stringify(nuovoForm))
salvaBozza(nuovoForm)
}, { deep: true })
function salvaBozza(dati: any) {
localStorage.setItem('bozza-form', JSON.stringify(dati))
}
</script>
Attenzione alla performance con deep watch
Il deep watch attraversa tutte le proprieta dell’oggetto ad ogni cambiamento. Per oggetti grandi, preferisci osservare proprieta specifiche.
// Meno performante: deep watch su oggetto grande
watch(statoGrande, callback, { deep: true })
// Piu performante: watch su proprieta specifiche
watch(
() => statoGrande.value.proprieta.specifica,
callback
)
Opzione once
Con once: true (Vue 3.4+), il watcher si attiva solo la prima volta.
watch(sorgente, (valore) => {
console.log('Attivato solo la prima volta:', valore)
}, { once: true })
watchEffect()
watchEffect() esegue una funzione immediatamente e la ri-esegue ogni volta che le sue dipendenze reattive cambiano. A differenza di watch, non richiede di specificare le sorgenti.
<script setup>
import { ref, watchEffect } from 'vue'
const ricerca = ref('')
const pagina = ref(1)
const risultati = ref([])
// Traccia automaticamente ricerca e pagina come dipendenze
watchEffect(async () => {
const risposta = await fetch(
`/api/ricerca?q=${ricerca.value}&pagina=${pagina.value}`
)
risultati.value = await risposta.json()
})
</script>
watchEffect con cleanup
Per gestire race condition o cancellare operazioni precedenti, usa la funzione onCleanup.
<script setup>
import { ref, watchEffect } from 'vue'
const ricerca = ref('')
const risultati = ref<string[]>([])
watchEffect((onCleanup) => {
const controller = new AbortController()
// Cancella la richiesta precedente se la ricerca cambia
onCleanup(() => controller.abort())
if (ricerca.value.length >= 3) {
fetch(`/api/ricerca?q=${ricerca.value}`, {
signal: controller.signal
})
.then(r => r.json())
.then(dati => { risultati.value = dati })
.catch(e => {
if (e.name !== 'AbortError') console.error(e)
})
}
})
</script>
watchPostEffect e watchSyncEffect
import { watchEffect, watchPostEffect, watchSyncEffect } from 'vue'
// Default: esegue prima dell'aggiornamento DOM
watchEffect(() => { /* ... */ })
// Post: esegue DOPO l'aggiornamento DOM (utile per accesso DOM)
watchPostEffect(() => {
// Il DOM e gia aggiornato qui
const elemento = document.getElementById('mio-elemento')
if (elemento) {
console.log('Altezza:', elemento.offsetHeight)
}
})
// Sync: esegue in modo sincrono (usare con cautela)
watchSyncEffect(() => { /* ... */ })
Fermare un watcher
I watcher creati in <script setup> vengono fermati automaticamente quando il componente viene smontato. Per fermarli manualmente, usa il valore di ritorno.
<script setup>
import { ref, watch, watchEffect } from 'vue'
const contatore = ref(0)
// watch restituisce una funzione di stop
const fermaWatch = watch(contatore, (val) => {
console.log('Contatore:', val)
if (val >= 10) {
fermaWatch() // Ferma il watcher quando raggiunge 10
}
})
// Lo stesso vale per watchEffect
const fermaEffect = watchEffect(() => {
console.log('Contatore:', contatore.value)
})
// Ferma manualmente quando serve
function pulisci() {
fermaWatch()
fermaEffect()
}
</script>
watch vs watchEffect
| Caratteristica | watch() | watchEffect() |
|---|---|---|
| Dipendenze | Esplicite | Automatiche |
| Esecuzione iniziale | No (serve immediate) |
Si (immediata) |
| Vecchio valore | Disponibile | Non disponibile |
| Pigro | Si | No |
| Uso ideale | Side effect specifici | Sincronizzazione dati |
Best practice
- Usa
computedper dati derivati: Se il risultato dipende da altri dati, usa computed - Usa
watchper side effect: Chiamate API, logging, manipolazione DOM - Preferisci
watchEffectper sincronizzazioni semplici: Quando le dipendenze sono ovvie - Evita deep watch su oggetti grandi: Osserva proprieta specifiche per performance migliori
- Gestisci sempre il cleanup: Cancella richieste HTTP e timer nei watcher asincroni
- Non modificare la sorgente osservata nel callback: Rischio di loop infiniti