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

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 .value ripetitivo
  • Non destrutturare reactive(): Usa toRefs() 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 a localStorage