f in x
> cd .. / HUB_EDITORIALE > Visualizza in Inglese
Analisi dei dati e metriche

Reactive refs, computed e watch in Vue 3: la guida operativa per non sbagliare

[2026-06-11] Author: Ing. Pietro Maiorana

Hai mai bloccato il browser con un watch che scatena un loop infinito? O creato uno stato reattivo che non si aggiorna quando pensi? Succede a tutti. Il sistema di reattività di Vue 3 è potente, ma va capito. Noi, di Meteora Web, lo usiamo ogni giorno nelle nostre piattaforme proprietarie – e abbiamo visto progetti rallentare o rompersi proprio perché ref(), computed e watch venivano usati senza criterio. In questa guida ti spieghiamo il perché e il come, con esempi reali che puoi copiare subito.

La triade reattiva: ref, computed e watch

Vue 3 offre tre strumenti fondamentali per gestire lo stato reattivo nella Composition API: ref() per dati mutabili, computed() per valori derivati e watch() / watchEffect() per eseguire effetti collaterali. Ognuno ha un ruolo preciso. Usarli bene significa codice più performante, meno bug e – cosa che ci sta a cuore – meno costi di manutenzione.

ref() e shallowRef(): scegli la profondità giusta

ref() rende reattivo un valore primitivo o un oggetto. Quando l'oggetto è annidato, Vue ne traccia ricorsivamente ogni proprietà. Sembra comodo, ma ha un costo: modificare una proprietà profonda innesca una ri-render dell'intero componente, anche se il resto dell'oggetto non cambia.

shallowRef() traccia solo il riferimento esterno: se assegni un nuovo oggetto, il componente si aggiorna; se modifichi una proprietà interna, no. È perfetto per dati immutabili o quando gestisci manualmente le notifiche con triggerRef().

import { ref, shallowRef, triggerRef } from 'vue'

// ref profondo: ogni modifica interna viene tracciata
const utente = ref({ nome: 'Mario', indirizzo: { città: 'Sciacca' } })
utente.value.indirizzo.città = 'Palermo' // trigger reattivo

// shallowRef: solo il riferimento esterno è reattivo
const ordini = shallowRef([{ id: 1, totale: 50 }])
// Per aggiornare, DEVI sostituire l'array o chiamare triggerRef
triggerRef(ordini) // notifica manualmente i watcher

Quando usare shallowRef? Quando hai liste lunghe o dati che vengono rimpiazzati interamente (es. array di ordini da API). Eviti la deep reactivity che consuma memoria e CPU inutilmente.

computed: mai scrivere effetti collaterali

Un computed restituisce un valore derivato. È lazy (calcola solo quando serve) e cache (se le dipendenze non cambiano, riusa il valore). Due regole d'oro:

  1. Non deve avere effetti collaterali (nessuna chiamata API, nessun logging).
  2. Non deve mutare lo stato reattivo (solo leggere).
import { ref, computed } from 'vue'

const prezzo = ref(100)
const quantità = ref(2)

// ✅ corretto: derivato puro, senza side effect
const totale = computed(() => prezzo.value * quantità.value)

// ❌ sbagliato: effetto collaterale dentro computed
const errato = computed(() => {
  quantità.value++ // muta lo stato! loop infinito
  return prezzo.value * quantità.value
})

Un errore comune è usare computed per filtrare liste lunghe dentro i template. Va bene, ma se la lista è enorme (migliaia di elementi) considera v-memo o una memoizzazione manuale. Noi, di Meteora Web, abbiamo ottimizzato dashboard con migliaia di righe passando da computed a funzioni non reattive aggiornate su azione esplicita.

watch e watchEffect: quando eseguire effetti collaterali

watch() osserva una o più fonti reattive e chiama una callback quando cambiano. Richiede di specificare la fonte (un getter). watchEffect() esegue la callback immediatamente e traccia automaticamente tutte le dipendenze usate al suo interno.

CaratteristicawatchwatchEffect
Accesso al vecchio valoreNo
Esecuzione inizialeNo (lazy)Sì (immediata)
Dipendenze espliciteDichiarateAutomatiche
Cleanup degli effettiCallback di cleanupCallback di cleanup
import { ref, watch, watchEffect } from 'vue'

const query = ref('')
const risultati = ref([])

// watch: utile per debounce o accesso a vecchio valore
watch(query, async (nuovo, vecchio) => {
  if (nuovo === vecchio) return
  risultati.value = await fetchData(nuovo)
})

// watchEffect: per log o sincronizzazioni semplici
watchEffect(() => {
  console.log('Query cambiata:', query.value)
  // eseguito subito, e ogni volta che query cambia
})

Attenzione a watch profondo: se passi { deep: true } su un oggetto grande, ogni modifica a qualsiasi proprietà farà scattare il watch. Meglio osservare un getter specifico:

// ❌ deep watch costoso
watch(utente, handler, { deep: true })

// ✅ getter mirato
watch(() => utente.value.indirizzo.città, handler)

Pattern avanzati per performance e affidabilità

Debounce di un watch per input utente

Se il tuo watch esegue una chiamata API a ogni digitazione, uccidi la UX e il budget API. Usa un debounce:

import { watch, ref } from 'vue'
import { debounce } from 'lodash-es' // o una funzione custom

const search = ref('')

const debouncedFetch = debounce((query) => {
  // chiamata API
}, 400)

watch(search, (val) => {
  debouncedFetch(val)
})

Cleanup con onWatcherCleanup (Vue 3.4+)

Da Vue 3.4, puoi usare onWatcherCleanup per annullare richieste asincrone quando il watcher viene ri-eseguito o il componente smontato:

import { watch, onWatcherCleanup } from 'vue'

watch(id, (newId) => {
  const controller = new AbortController()
  onWatcherCleanup(() => controller.abort())
  fetch(`/api/utente/${newId}`, { signal: controller.signal })
})

Reattività in strutture annidate e performance

Quando lavori con store (ad esempio con Pinia o store reattivi personalizzati), ricordati che computed non è sempre la risposta. Se devi calcolare un aggregato su una lista di migliaia di oggetti, e la lista cambia raramente, valuta un watch che aggiorna una ref manualmente – eviti ricalcoli reattivi a ogni flusso.

const prodotti = ref([/* lunga lista */])
const totaleCarrello = ref(0)

// computed sarebbe calcolato a ogni accesso al template
// ma se vogliamo aggiornarlo solo quando la lista cambia:
watch(prodotti, (nuovaLista) => {
  totaleCarrello.value = nuovaLista.reduce((acc, p) => acc + p.prezzo, 0)
}, { immediate: true })

Noi, di Meteora Web, usiamo questa tecnica in piattaforme di e-commerce per tenere sotto controllo il carrello senza spese di calcolo inutili.

Errori comuni e come evitarli

  • Usare ref() invece di reactive() per oggetti grandi: ref avvolge l'oggetto in un proxy, ma accediamo con .value. reactive è più diretto, ma richiede attenzione con lo spread. Noi preferiamo ref perché è esplicito e si sposa meglio con la composizione.
  • Dimenticare .value nei template di script setup: Nei template, ref viene auto-unwrapped, ma nel setup script devi usare .value. Errore classico.
  • WatchEffect con effetti asincroni senza gestione race condition: se la callback è async, ogni nuova esecuzione non aspetta la precedente. Usa un flag o AbortController.
  • Computed muta lo stato: non farlo mai. Se devi trasformare dati, crea un nuovo oggetto.
  • ShallowRef dimenticando triggerRef: se non notifichi manualmente, il componente non si aggiorna.

In sintesi — cosa fare adesso

  1. Rivedi ogni computed: se contiene console.log, fetch, o mutate lo stato, spostalo in un watch.
  2. Sostituisci ref() con shallowRef() per array lunghi o dati che sostituisci interamente. Aggiungi triggerRef quando necessario.
  3. Usa watch con getter specifico invece di deep: true per oggetti annidati.
  4. Aggiungi debounce a tutti i watch che fanno chiamate API in risposta a input utente.
  5. Implementa cleanup con onWatcherCleanup per evitare risposte obsolete e memory leak.

Sponsored Protocol

Ing. Pietro Maiorana

> AUTHOR_EXTRACTED

Ing. Pietro Maiorana

Co-founder di Meteora Web e Ingegnere Informatico. Insieme a Calogero Bono porta competenze tecniche di alto livello nel digital marketing e nello sviluppo.

[ Read Full Dossier ]

Hai bisogno di applicare questa strategia?

Esegui il protocollo di contatto per iniziare un progetto con noi.

> INIZIA_PROGETTO

Sponsored

> MW_JOURNAL

> READ_ALL()