00
:
00
:
00
:
00
Corso SEO AI - Usa SEOEMAIL al checkout per il 30% di sconto

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 computed per dati derivati: Se il risultato dipende da altri dati, usa computed
  • Usa watch per side effect: Chiamate API, logging, manipolazione DOM
  • Preferisci watchEffect per 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