ref() e reactive()
Il sistema di reattivita
Vue 3 utilizza un sistema di reattivita basato su JavaScript Proxy che traccia automaticamente le dipendenze e aggiorna il DOM quando i dati cambiano. Le due funzioni principali sono ref() e reactive().
ref()
ref() crea un riferimento reattivo per qualsiasi tipo di valore: primitivi (stringhe, numeri, booleani) e oggetti.
<script setup>
import { ref } from 'vue'
// Primitivi
const contatore = ref(0)
const nome = ref('Mario')
const isAttivo = ref(true)
// Oggetti e array
const utente = ref({ nome: 'Mario', eta: 30 })
const lista = ref([1, 2, 3])
// Accesso al valore in script: usa .value
console.log(contatore.value) // 0
contatore.value++
console.log(contatore.value) // 1
// Modifica oggetti
utente.value.eta = 31
utente.value = { nome: 'Luigi', eta: 28 } // Sostituzione completa
// Modifica array
lista.value.push(4)
lista.value = [...lista.value, 5]
</script>
<template>
<!-- Nel template: .value NON serve, Vue lo fa automaticamente -->
<p>Contatore: {{ contatore }}</p>
<p>Nome: {{ nome }}</p>
<p>Utente: {{ utente.nome }}, {{ utente.eta }} anni</p>
<button @click="contatore++">Incrementa</button>
</template>
Tipizzare ref con TypeScript
import { ref } from 'vue'
// Inferenza automatica
const contatore = ref(0) // Ref<number>
const nome = ref('Mario') // Ref<string>
// Tipo esplicito
const eta = ref<number | null>(null)
// Interfacce
interface Utente {
id: number
nome: string
email: string
}
const utente = ref<Utente>({
id: 1,
nome: 'Mario',
email: 'mario@example.com'
})
// Array tipizzato
const utenti = ref<Utente[]>([])
utenti.value.push({ id: 2, nome: 'Luigi', email: 'luigi@example.com' })
reactive()
reactive() crea un oggetto reattivo profondo. Funziona solo con oggetti, array, Map e Set. Non funziona con primitivi.
<script setup>
import { reactive } from 'vue'
// Oggetto reattivo
const stato = reactive({
contatore: 0,
utente: {
nome: 'Mario',
indirizzo: {
citta: 'Roma',
cap: '00100'
}
},
lista: ['item1', 'item2']
})
// Accesso diretto senza .value
console.log(stato.contatore) // 0
stato.contatore++
// Reattivita profonda: anche gli oggetti annidati sono reattivi
stato.utente.indirizzo.citta = 'Milano'
// Modifica array
stato.lista.push('item3')
</script>
<template>
<p>Contatore: {{ stato.contatore }}</p>
<p>Citta: {{ stato.utente.indirizzo.citta }}</p>
<button @click="stato.contatore++">Incrementa</button>
</template>
Limitazioni di reactive()
import { reactive } from 'vue'
// NON funziona con primitivi
// const contatore = reactive(0) // Errore
// Attenzione alla destrutturazione: perde reattivita
const stato = reactive({ nome: 'Mario', eta: 30 })
// Perdita di reattivita!
let { nome, eta } = stato
nome = 'Luigi' // Non aggiorna il template
// Attenzione alla riassegnazione: perde reattivita
let statoForm = reactive({ campo1: '', campo2: '' })
// statoForm = reactive({ campo1: 'nuovo', campo2: 'nuovo' }) // Perde il riferimento
// Soluzione: modifica le proprieta singolarmente
statoForm.campo1 = 'nuovo'
statoForm.campo2 = 'nuovo'
// Oppure usa Object.assign
Object.assign(statoForm, { campo1: 'nuovo', campo2: 'nuovo' })
toRef() e toRefs()
Queste utility mantengono la connessione reattiva quando si estraggono proprieta da un oggetto reactive.
toRef()
Crea un ref collegato a una singola proprieta di un oggetto reattivo.
<script setup>
import { reactive, toRef } from 'vue'
const stato = reactive({
nome: 'Mario',
eta: 30
})
// Crea un ref collegato a stato.nome
const nomeRef = toRef(stato, 'nome')
// Le modifiche si propagano in entrambe le direzioni
nomeRef.value = 'Luigi'
console.log(stato.nome) // 'Luigi'
stato.nome = 'Peach'
console.log(nomeRef.value) // 'Peach'
</script>
toRefs()
Converte tutte le proprieta di un oggetto reattivo in ref individuali, mantenendo la connessione.
<script setup>
import { reactive, toRefs } from 'vue'
const stato = reactive({
nome: 'Mario',
eta: 30,
email: 'mario@example.com'
})
// Destrutturazione sicura con toRefs
const { nome, eta, email } = toRefs(stato)
// Ora sono ref collegati all'oggetto originale
nome.value = 'Luigi' // Aggiorna anche stato.nome
</script>
<template>
<p>{{ nome }} - {{ eta }} anni</p>
<p>{{ email }}</p>
</template>
Uso pratico con composables
// composables/useUtente.ts
import { reactive, toRefs } from 'vue'
export function useUtente() {
const stato = reactive({
nome: '',
email: '',
isCaricamento: false,
errore: null as string | null
})
async function caricaUtente(id: number) {
stato.isCaricamento = true
stato.errore = null
try {
const risposta = await fetch(`/api/utenti/${id}`)
const dati = await risposta.json()
stato.nome = dati.nome
stato.email = dati.email
} catch (e) {
stato.errore = 'Errore nel caricamento'
} finally {
stato.isCaricamento = false
}
}
// Restituisce toRefs per consentire destrutturazione nel componente
return {
...toRefs(stato),
caricaUtente
}
}
<script setup>
import { useUtente } from '@/composables/useUtente'
// Destrutturazione sicura grazie a toRefs
const { nome, email, isCaricamento, errore, caricaUtente } = useUtente()
</script>
shallowRef e shallowReactive
Per motivi di performance, Vue offre versioni “superficiali” che non rendono reattive le proprieta annidate.
shallowRef()
Solo la sostituzione di .value e reattiva, non le modifiche alle proprieta interne.
<script setup>
import { shallowRef, triggerRef } from 'vue'
const stato = shallowRef({
profondo: {
contatore: 0
}
})
// Questo NON attiva aggiornamenti del template
stato.value.profondo.contatore++
// Questo SI (sostituzione completa di .value)
stato.value = {
profondo: {
contatore: stato.value.profondo.contatore + 1
}
}
// In alternativa, forza l'aggiornamento manualmente
stato.value.profondo.contatore++
triggerRef(stato) // Forza il trigger della reattivita
</script>
shallowReactive()
Solo le proprieta di primo livello sono reattive.
import { shallowReactive } from 'vue'
const stato = shallowReactive({
contatore: 0, // Reattivo
profondo: {
valore: 'ciao' // NON reattivo
}
})
stato.contatore++ // Attiva aggiornamento
stato.profondo.valore = 'nuovo' // NON attiva aggiornamento
stato.profondo = { valore: 'nuovo' } // Attiva aggiornamento (proprieta primo livello)
Quando usare shallow
- Grandi strutture dati: Oggetti con migliaia di proprieta annidate
- Dati esterni: Oggetti provenienti da librerie esterne che non devono essere osservati in profondita
- Ottimizzazione performance: Quando sai che le modifiche avverranno solo al primo livello
ref() vs reactive(): quando usare cosa
Preferisci ref() quando
import { ref } from 'vue'
// Valori primitivi (unica opzione)
const contatore = ref(0)
const nome = ref('Mario')
const isVisibile = ref(true)
// Valori che verranno sostituiti
const datiUtente = ref<Utente | null>(null)
datiUtente.value = await fetchUtente()
// Composables (il pattern standard)
function useContatore(iniziale = 0) {
const contatore = ref(iniziale)
function incrementa() { contatore.value++ }
return { contatore, incrementa }
}
Preferisci reactive() quando
import { reactive } from 'vue'
// Stato di un form (molte proprieta correlate)
const form = reactive({
nome: '',
email: '',
password: '',
confermaPassword: ''
})
// Stato complesso del componente
const stato = reactive({
filtri: { cerca: '', categoria: 'tutti' },
paginazione: { pagina: 1, perPagina: 10 },
ordinamento: { campo: 'nome', direzione: 'asc' as 'asc' | 'desc' }
})
Riepilogo comparativo
| Caratteristica | ref() | reactive() |
|---|---|---|
| Primitivi | Si | No |
| Oggetti/Array | Si | Si |
| Accesso in script | .value |
Diretto |
| Accesso in template | Automatico | Diretto |
| Destrutturabile | N/A | No (serve toRefs) |
| Riassegnabile | Si (ref.value = ...) |
No |
| Deep reactivity | Si | Si |
| TypeScript | Ottimo | Buono |
Utility di reattivita
import {
isRef,
isReactive,
isProxy,
isReadonly,
unref,
toRaw
} from 'vue'
const contatore = ref(0)
const stato = reactive({ x: 1 })
// Controlli tipo
isRef(contatore) // true
isReactive(stato) // true
isProxy(stato) // true
// unref: estrae il valore da un ref (o restituisce il valore se non e un ref)
const valore = unref(contatore) // 0
// Equivalente a: isRef(val) ? val.value : val
// toRaw: restituisce l'oggetto originale non reattivo
const oggettoRaw = toRaw(stato)
// Utile per serializzazione o passaggio a librerie esterne
console.log(JSON.stringify(toRaw(stato)))
Best practice
- Usa
ref()come default: E piu versatile e funziona con tutti i tipi - Usa
reactive()per form e stato complesso: Evita il.valueripetitivo - Non destrutturare
reactive(): UsatoRefs()se serve la destrutturazione - Usa
shallowRef()per dati grandi: Migliora la performance con strutture complesse - Preferisci
ref()nei composables: Rende il codice piu prevedibile e componibile - Usa
toRaw()per serializzazione: Quando devi inviare dati a un’API o alocalStorage