Le basi della Reattività
Preferenza dull'API
Questa pagina e molti altri capitoli più avanti nella guida contengono contenuti diversi per l'Options API e la Composition API. La tua preferenza attuale è Composition API. Puoi alternare tra gli stili delle API utilizzando gli interruttori "Preferenza API" nella parte superiore della barra laterale sinistra.
Dichiarare lo Stato Reattivo
ref()
Nella Composition API, il modo consigliato per dichiarare lo stato reattivo è utilizzare la funzione ref()
:
js
import { ref } from 'vue'
const count = ref(0)
ref()
prende l'argomento e lo restituisce racchiuso in un oggetto ref con una proprietà .value
:
js
const count = ref(0)
console.log(count) // { value: 0 }
console.log(count.value) // 0
count.value++
console.log(count.value) // 1
Vedi anche: Typing Refs
Per accedere alle ref nel template di un componente, bisogna dichiararle e restituirle tramite la funzione setup()
del componente stesso:
js
import { ref } from 'vue'
export default {
// `setup` è un hook speciale dedicato alla Composition API.
setup() {
const count = ref(0)
// espone la ref per il template
return {
count
}
}
}
template
<div>{{ count }}</div>
Da notare che non abbiamo avuto bisogno di aggiungere .value
quando abbiamo utilizzato la ref nel template. Per comodità, le ref vengono automaticamente estratte quando utilizzate all'interno dei template (con alcune limitazioni).
Puoi anche modificare direttamente una ref nei gestori di eventi (event handlers):
template
<button @click="count++">
{{ count }}
</button>
Per una logica più complessa, possiamo dichiarare funzioni che modificano le ref nello stesso scope ed esporle come metodi insieme allo stato:
js
import { ref } from 'vue'
export default {
setup() {
const count = ref(0)
function increment() {
// .value è necessario in JavaScript
count.value++
}
// non dimenticare di esporre anche la funzione.
return {
count,
increment
}
}
}
I metodi esposti possono quindi essere utilizzati come gestori di eventi (event handlers):
template
<button @click="increment">
{{ count }}
</button>
Ecco l'esempio dal vivo su Codepen, senza utilizzare alcuno strumento di compilazione.
<script setup>
Esporre manualmente lo stato e i metodi tramite setup()
può essere verboso. Fortunatamente, ciò può essere evitato quando si utilizzano Componenti Single-File (SFC). In questo caso possiamo semplificarne l'uso con <script setup>
:
vue
<script setup>
import { ref } from 'vue'
const count = ref(0)
function increment() {
count.value++
}
</script>
<template>
<button @click="increment">
{{ count }}
</button>
</template>
Gli import, le variabili e le funzioni di primo livello dichiarate in <script setup>
sono automaticamente utilizzabili nel template dello stesso componente. Pensa al template come a una funzione JavaScript dichiarata nello stesso scope: ha automaticamente accesso a tutto ciò che in esso è dichiarato.
TIP
Per il resto della guida utilizzeremo principalmente la sintassi SFC + <script setup>
per gli esempi di codice della Composition API, dato che è l'uso più comune per gli sviluppatori Vue.
Se non stai utilizzando SFC, puoi comunque utilizzare la Composition API con l'opzione setup()
.
Perché le Ref?
Potresti chiederti perché abbiamo bisogno delle ref con la proprietà .value
invece di semplici variabili. Per spiegarlo dovremo analizzare brevemente il funzionamento del sistema di reattività di Vue.
Quando utilizzi una ref in un template e ne cambi il valore successivamente, Vue rileva automaticamente la modifica e aggiorna il DOM di conseguenza. Ciò è reso possibile da un sistema di reattività basato sul tracciamento delle dipendenze. Quando un componente viene renderizzato per la prima volta, Vue traccia ogni ref che è stata utilizzata durante il rendering. Successivamente, quando una ref viene modificata, essa innescherà un nuovo rendering per i componenti che la stanno tracciando.
Nel JavaScript standard non c'è modo di rilevare l'accesso o la modifica di semplici variabili. Tuttavia, possiamo intercettare le operazioni di get e set delle proprietà di un oggetto utilizzando i metodi getter e setter.
La proprietà .value
dà a Vue la possibilità di rilevare quando si accede a una ref o quando viene modificata. Dietro le quinte Vue esegue il tracciamento nel suo getter e innesca l'aggiornamento nel suo setter. Concettualmente, puoi pensare a una ref come a un oggetto che assomiglia a questo:
js
// pseudo codice, non è una implementazione reale
const myRef = {
_value: 0,
get value() {
track()
return this._value
},
set value(newValue) {
this._value = newValue
trigger()
}
}
Un altro aspetto interessante delle refs è che, a differenza delle semplici variabili, puoi passare le refs nelle funzioni mantenendo sia l'accesso all'ultimo valore che la loro natura reattiva. Questo si rivela particolarmente utile quando devi trasformare una parte di codice complessa in una struttura riutilizzabile.
Il sistema di reattività è discusso più in dettaglio nella sezione La Reattività in dettaglio.
La Reattività Avanzata
Le refs possono contenere qualsiasi tipo di valore, inclusi oggetti profondamente annidati, array o altre strutture dati native di JavaScript come le Map
.
Una ref farà si che il suo valore sarà profondamente reattivo. Ciò significa che i cambiamenti saranno rilevati anche quando modifichi oggetti annidati o array:
js
import { ref } from 'vue'
const obj = ref({
nested: { count: 0 },
arr: ['foo', 'bar']
})
function mutateDeeply() {
// questi funzioneranno come previsto.
obj.value.nested.count++
obj.value.arr.push('baz')
}
I valori non primitivi vengono trasformati in proxy reattivi tramite reactive()
, che sarà spiegato di seguito.
È anche possibile scegliere di non utilizzare la reattività avanzata con le shallow refs. Per quanto riguarda le refs shallow (superficiali), per la reattività viene viene monitorato solo l'accesso al .value
. Le refs shallow possono essere utilizzate per ottimizzare le prestazioni, evitando il costo dell'osservazione di grandi oggetti, o nei casi in cui lo stato interno è gestito da una libreria esterna.
Letture aggiuntive:
DOM e tempi di Aggiornamento
Quando modifichi lo stato reattivo il DOM viene aggiornato automaticamente. Tuttavia, è importante notare che gli aggiornamenti del DOM non vengono applicati in modo sincrono. Vue li mette, invece, in un buffer fino al "next tick" del ciclo di aggiornamento, così da garantire che ogni componente venga aggiornato solo una volta, indipendentemente dal numero di modifiche allo stato che hai effettuato.
Per aspettare che l'aggiornamento del DOM sia completato, dopo una modifica dello stato, puoi utilizzare l'API globale nextTick():
js
import { nextTick } from 'vue'
async function increment() {
count.value++
await nextTick()
// Ora il DOM è aggiornato
}
reactive()
C'è un altro modo per indicare la reattività dello stato ed è tramite l'API reactive()
. A differenza di una ref, che racchiude il valore interno in un oggetto speciale, reactive()
rende l'oggetto stesso reattivo:
js
import { reactive } from 'vue'
const state = reactive({ count: 0 })
Vedi anche: Tipizzazione Reattiva
Utilizzo nel template:
template
<button @click="state.count++">
{{ state.count }}
</button>
Gli oggetti reattivi sono Proxy JavaScript e si comportano esattamente come dei normali oggetti. La differenza è che Vue è in grado di intercettare l'accesso e la modifica di tutte le proprietà di un oggetto reattivo, per il tracciamento della reattività e l'attivazione.
reactive()
converte profondamente l'oggetto: anche gli oggetti annidati vengono avvolti con reactive()
quando vi si accede. reactive()
viene invocato internamente da ref()
quando il valore contenuto nella ref è un oggetto. Similmente alle refs shallow, c'è anche l'API shallowReactive()
per scegliere di non utilizzare la reattività profonda.
reactive()
converts the object deeply: nested objects are also wrapped with reactive()
when accessed. It is also called by ref()
internally when the ref value is an object. Similar to shallow refs, there is also the shallowReactive()
API for opting-out of deep reactivity.
Proxy Reattiva vs. Originale
È importante notare che il valore restituito da reactive()
è un Proxy dell'oggetto originale, che non è uguale all'oggetto originale:
js
const raw = {}
const proxy = reactive(raw)
// il proxy NON è uguale all'originale.
console.log(proxy === raw) // false
Solo il proxy è reattivo - modificare l'oggetto originale non attiverà gli aggiornamenti. Quindi, quando si lavora con il sistema di reattività di Vue il modo migliore è di utilizzare esclusivamente le versioni proxy del tuo stato.
Per garantire un accesso coerente al proxy, chiamare reactive()
sullo stesso oggetto restituirà sempre lo stesso proxy, e chiamare reactive()
su un proxy esistente restituirà lo stesso proxy:
js
// chiamare reactive() sullo stesso oggetto restituisce lo stesso proxy
console.log(reactive(raw) === proxy) // true
// chiamare reactive() su un proxy restituisce se stesso
console.log(reactive(proxy) === proxy) // true
Questa regola si applica anche agli oggetti annidati. A causa della reattività profonda, gli oggetti annidati all'interno di un oggetto reattivo sono anch'essi dei proxy:
js
const proxy = reactive({})
const raw = {}
proxy.nested = raw
console.log(proxy.nested === raw) // false
Limitazioni di reactive()
L'API reactive()
presenta alcune limitazioni:
Valori di tipizzazione limitati: funziona solo per i type
oggetto
(oggetti, array e type collezioni comeMap
eSet
). Non può contenere type primitivi comestring
,number
oboolean
.Impossibilità di sostituire l'intero oggetto: poiché il tracciamento della reattività di Vue funziona attraverso l'accesso alle proprietà, dobbiamo sempre mantenere lo stesso riferimento all'oggetto reattivo. Ciò significa che non possiamo "sostituire" facilmente un oggetto reattivo perché la connessione della reattività al primo riferimento sarà persa:
jslet state = reactive({ count: 0 }) // il riferimento sopra ({ count: 0 }) non viene più tracciato // (la connessione della reattività è persa!) state = reactive({ count: 1 })
Non compatibile con la destrutturazione: quando destrutturiamo una proprietà di tipo primitivo di un oggetto reattivo in variabili locali, o quando passiamo quella proprietà a una funzione, perderemo la connessione della reattività:
jsconst state = reactive({ count: 0 }) // count è disconnesso da state.count quando viene destrutturato. let { count } = state // non influisce sullo stato originale count++ // la funzione riceve un numero normale e // non sarà in grado di tracciare le modifiche a state.count // dobbiamo passare l'intero oggetto per mantenere la reattività callSomeFunction(state.count)
A causa di queste limitazioni, raccomandiamo di utilizzare ref()
come API principale per dichiarare lo stato reattivo.
Dettagli sull'Estrazione delle Ref
Come Proprietà di un Oggetto Reattivo
Una ref viene automaticamente estratta (unwrapped) quando vi si accede o la si modifica come proprietà di un oggetto reattivo. In altre parole, si comporta come una normale proprietà:
js
const count = ref(0)
const state = reactive({
count
})
console.log(state.count) // 0
state.count = 1
console.log(count.value) // 1
Se viene assegnata una nuova ref a una proprietà collegata a una ref esistente, questa sostituirà la vecchia ref:
js
const otherCount = ref(2)
state.count = otherCount
console.log(state.count) // 2
// la ref originale è ora disconnessa da state.count
console.log(count.value) // 1
L'estrazione (unwrapping) delle ref avviene solo quando sono annidate all'interno di un oggetto reattivo profondo. Non si applica quando vi si accede come proprietà di un oggetto reattivo shallow.
Eccezione con Array e Collezioni
A differenza degli oggetti reattivi, non viene eseguita alcuna estrazione quando la ref viene letta come elemento di un array reattivo o di un tipo di collezione nativo come Map
:
js
const books = reactive([ref('Vue 3 Guide')])
// è necessario .value qui
console.log(books[0].value)
const map = reactive(new Map([['count', ref(0)]]))
// è necessario .value qui
console.log(map.get('count').value)
Eccezione con i Template
L'estrazione (unwrapping) delle ref nei template si applica solo se la ref è una proprietà di livello superiore nel contesto di rendering del template.
Nell'esempio qui sotto, count
e object
sono proprietà di livello superiore, ma object.id
non lo è:
js
const count = ref(0)
const object = { id: ref(0) }
Pertanto, questa espressione funziona come previsto:
template
{{ count + 1 }}
...mentre questa NO:
template
{{ object.id + 1 }}
Il risultato renderizzato sarà [object Object]1
perché object.id
non viene estratto durante la valutazione dell'espressione e rimane un oggetto ref. Per correggere questo comportamento, possiamo destrutturare id
in una proprietà di livello superiore:
js
const { id } = object
template
{{ id + 1 }}
Ora il risultato del rendering sarà 2
.
Un'altra cosa da notare è che una ref viene estratta se è il valore finale valutato di un'interpolazione di testo (cioè un tag {{ }}
), quindi il seguente codice renderizzerà 1
:
template
{{ object.id }}
Questa funzionalità ha solo un carattere di praticità per per favorire l'interpolazione di testo ed è equivalente a {{ object.id.value }}
.