Fondamentaux de la réactivité
Préférence d'API
Cette page et de nombreux autres chapitres plus loin dans le guide contiennent un contenu différent pour l'Options API et la Composition API. Actuellement, votre préférence est la Composition API. Vous pouvez passer d'un style d'API à l'autre à l'aide des boutons "Préférence d'API" situés en haut de la barre latérale gauche.
Déclarer un état réactif
ref()
Avec la Composition API, la méthode recommandée pour déclarer l'état réactif consiste à utiliser la fonction ref()
:
js
import { ref } from 'vue'
const count = ref(0)
ref()
prend l'argument et le renvoie enveloppé dans un objet ref avec une propriété .value
:
js
const count = ref(0)
console.log(count) // { value: 0 }
console.log(count.value) // 0
count.value++
console.log(count.value) // 1
Voir aussi : Typer les variables réactives
Pour utiliser un état réactif dans le template d'un composant, déclarez et renvoyez-le depuis la fonction setup()
du composant :
js
import { ref } from 'vue'
export default {
// `setup` est un hook spécial dédié à la Composition API.
setup() {
const count = ref(0)
// expose l'état au template
return {
count
}
}
}
template
<div>{{ count }}</div>
Notez que nous n'avons pas besoin d'ajouter .value
lors de l'utilisation de la ref dans le template. Pour plus de commodité, les refs sont automatiquement déballées lorsqu'elles sont utilisées dans des templates (avec quelques pièges).
Vous pouvez également muter une ref directement dans les gestionnaires d'événements :
template
<button @click="count++">
{{ count }}
</button>
Pour une logique plus complexe, nous pouvons déclarer des fonctions qui modifient les ref dans la même portée et les exposer en tant que méthodes à côté de l'état :
js
import { ref } from 'vue'
export default {
setup() {
const count = ref(0)
function increment() {
// .value est nécessaire en JavaScript
count.value++
}
// n'oubliez pas d'également exposer la fonction.
return {
count,
increment
}
}
}
Les méthodes exposées sont généralement utilisées comme écouteurs d'événements :
template
<button @click="increment">
{{ count }}
</button>
Voici l'exemple sur Codepen, sans utiliser d'outils de build.
<script setup>
Exposer manuellement l'état et les méthodes via setup()
peut être verbeux. Heureusement, cela peut être évité avec l'utilisation de composants monofichiers (SFC). Nous pouvons simplifier l'utilisation avec <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>
Les importations de premier niveau et les variables déclarées dans <script setup>
sont automatiquement utilisables dans le template du même composant. Considérez le template comme une fonction JavaScript déclarée dans la même portée - il a naturellement accès à tout ce qui est déclaré à ses côtés.
TIP
Pour le reste du guide, nous utiliserons principalement la syntaxe monofichier + <script setup>
pour les exemples de code de la Composition API, car c'est l'utilisation la plus courante pour les développeurs Vue.
Si vous n'utilisez pas SFC, vous pouvez toujours utiliser la Composition API avec l'option setup()
.
Pourquoi Refs ?
Vous vous demandez peut-être pourquoi nous avons besoin de refs avec le .value
au lieu de simples variables. Pour l'expliquer, nous allons devoir discuter brièvement du fonctionnement du système de réactivité de Vue.
Lorsque vous utilisez une ref dans le template et que vous modifiez la valeur de la ref ultérieurement, Vue détecte automatiquement le changement et met à jour le DOM en conséquence. Ceci est possible grâce à un système de réactivité basé sur le suivi des dépendances. Lorsqu'un composant est rendu pour la première fois, Vue trace toutes les refs utilisées pendant le rendu. Plus tard, lorsqu'une ref est modifiée, elle déclenche un nouveau rendu pour les composants qui la suivent.
En JavaScript standard, il n'existe aucun moyen de détecter l'accès ou la mutation de variables simples. Mais il est possible d'intercepter les opérations get et set d'une propriété.
La propriété .value
donne à Vue la possibilité de détecter quand une ref a été consultée ou mutée. Sous le capot, Vue effectue le suivi dans son getter, et effectue le déclenchement dans son setter. Conceptuellement, vous pouvez considérer une ref comme un objet qui ressemble à ceci :
js
// pseudo-code, pas la mise en œuvre effective
const myRef = {
_value: 0,
get value() {
track()
return this._value
},
set value(newValue) {
this._value = newValue
trigger()
}
}
Une autre caractéristique intéressante des refs est que, contrairement aux variables ordinaires, vous pouvez passer des refs dans des fonctions tout en conservant l'accès à la dernière valeur et à la connexion de la réactivité. Cela s'avère particulièrement utile lors de la refonte d'une logique complexe en un code réutilisable.
Le système de réactivité est décrit plus en détail dans la section La réactivité en détails.
Réactivité profonde
Les refs peuvent contenir n'importe quel type de valeur, y compris des objets profondément imbriqués, des tableaux ou des structures de données JavaScript natives comme Map
.
Une ref rendra sa valeur profondément réactive. Cela signifie que vous pouvez vous attendre à ce que les changements soient détectés même lorsque vous mutez des objets imbriqués ou des tableaux :
js
import { ref } from 'vue'
const obj = ref({
nested: { count: 0 },
arr: ['foo', 'bar']
})
function mutateDeeply() {
// cela va fonctionner comme prévu.
obj.value.nested.count++
obj.value.arr.push('baz')
}
Les valeurs non primitives sont transformées en proxies réactifs via reactive()
, ce qui est expliqué ci-dessous.
Il est également possible de renoncer à la réactivité profonde avec shallow refs. Pour les refs peu profondes, seul l'accès à .value
est suivi pour la réactivité. Les refs peu profondes peuvent être utilisées pour optimiser les performances en évitant le coût d'observation des gros objets, ou dans les cas où l'état interne est géré par une bibliothèque externe.
Pour en savoir plus :
Timing de mise à jour du DOM
Lorsque vous modifiez un état réactif, le DOM est automatiquement mis à jour. Toutefois, il convient de noter que les mises à jour du DOM ne sont pas appliquées de manière synchrone. En effet, Vue les met en mémoire tampon jusqu'au prochain "tick" du cycle de mises à jour pour s'assurer que chaque composant ne soit mis à jour qu'une seule fois, quel que soit le nombre de modifications d'état que vous avez effectuées.
Pour attendre que la mise à jour du DOM soit terminée après un changement d'état, vous pouvez utiliser l'API globale nextTick() :
js
import { nextTick } from 'vue'
async function increment() {
count.value++
await nextTick()
// Maintenant le DOM est mis à jour
}
reactive()
Il existe une autre façon de déclarer un état réactif, avec l'API reactive()
. Contrairement à une ref qui enveloppe la valeur interne dans un objet spécial, reactive()
rend un objet lui-même réactif :
js
import { reactive } from 'vue'
const state = reactive({ count: 0 })
Voir aussi : Typer reactive()
Utilisation dans le template :
template
<button @click="state.count++">
{{ state.count }}
</button>
Les objets réactifs sont des proxys JavaScript et se comportent comme des objets classiques. La différence est que Vue est capable de traquer l'accès aux propriétés et les mutations d'un objet réactif.
reactive()
convertit l'objet en profondeur : les objets imbriqués sont également enveloppés par reactive()
lorsqu'on y accède. Il est également appelé par ref()
en interne lorsque la valeur de ref est un objet. Comme pour les refs peu profondes, il existe aussi l'API shallowReactive()
pour choisir de ne pas utiliser la réactivité profonde.
Proxy réactif vs. original
Il est important de noter que la valeur retournée par reactive()
est un proxy de l'objet original, qui n'est pas égal à l'objet original :
js
const raw = {}
const proxy = reactive(raw)
// le proxy n'est PAS égal à l'original.
console.log(proxy === raw) // faux
Seul le proxy est réactif - muter l'objet original ne déclenchera pas de mises à jour. Par conséquent, la meilleure pratique pour travailler avec le système de réactivité de Vue est d'utiliser exclusivement les versions proxifiées de votre état.
Pour assurer un accès cohérent au proxy, appeler reactive()
sur le même objet retournera toujours le même proxy, et appeler reactive()
sur un proxy existant retournera également ce même proxy :
js
// appeler reactive() sur le même objet retourne le même proxy
console.log(reactive(raw) === proxy) // true
// appeler reactive() sur un proxy se retourne lui-même
console.log(reactive(proxy) === proxy) // vrai
Cette règle s'applique tout aussi bien aux objets imbriqués. En raison de la réactivité profonde, les objets imbriqués à l'intérieur d'un objet réactif sont également des proxys :
js
const proxy = reactive({})
const raw = {}
proxy.nested = raw
console.log(proxy.nested === raw) // false
Limitations de reactive()
L'API reactive()
a deux limitations :
Types limités : Elle ne fonctionne que pour les types d'objets (objets, tableaux et objets de type collections tels que
Map
etSet
). Elle ne peut pas contenir les types primitifs tels questring
,number
ouboolean
.Remplacement impossible de l'intégralité de l'objet : Comme le suivi de la réactivité de Vue fonctionne sur l'accès aux propriétés, nous devons toujours conserver la même référence à l'objet réactif. Cela signifie que nous ne pouvons pas facilement "remplacer" un objet réactif car la connexion de réactivité à la première référence serait perdue :
jslet state = reactive({ count: 0 }) // la référence précédente ({ count: 0 }) n'est plus suivie // (la connexion de réactivité est perdue !) state = reactive({ count: 1 })
Ne fonctionne pas avec la destructuration : lorsque nous déstructurons la propriété d'un objet réactif en variables locales, ou lorsque nous passons cette propriété dans une fonction, nous perdons la connexion à la réactivité :
jsconst state = reactive({ count: 0 }) // count est déconnecté de state.count lorsqu'il est déstructuré. let { count } = state // n'affecte pas l'état original count++ // la fonction reçoit un simple nombre et // ne sera pas capable de traquer les changements de state.count // nous devons passer l'objet entier pour conserver la réactivité callSomeFunction(state.count)
À cause de ces limites, nous recommandons l'usage de ref()
en tant qu'API principale pour déclarer un état réactif.
En tant que propriété d'un objet réactif
Une ref est automatiquement déballée lorsqu'elle est accédée ou mutée en tant que propriété d'un objet réactif. En d'autres termes, elle se comporte comme une propriété normale :
js
const count = ref(0)
const state = reactive({
count
})
console.log(state.count) // 0
state.count = 1
console.log(count.value) // 1
Si une nouvelle ref est attribuée à une propriété liée à une ref existante, elle remplacera l'ancienne ref :
js
const otherCount = ref(2)
state.count = otherCount
console.log(state.count) // 2
// la ref originale est maintenant déconnectée de state.count
console.log(count.value) // 1
Le déballage de ref ne se produit que lorsqu'elle est imbriquée dans un objet réactif profond. Il ne s'applique pas lorsqu'elle est accédée en tant que propriété d'une shallowReactive.
Pièges lors de déballage de tableaux et collections
Contrairement aux objets réactifs, il n'y a pas de déballage effectué lorsque la ref est accédée en tant qu'élément d'un tableau réactif ou d'un type de collection natif comme Map
:
js
const books = reactive([ref('Vue 3 Guide')])
// il faut ici .value
console.log(books[0].value)
const map = reactive(new Map([['count', ref(0)]]))
// il faut ici .value
console.log(map.get('count').value)
Le déballage de ref dans les templates ne s'applique que si la ref est une propriété de premier niveau dans le contexte de rendu du template.
Dans l'exemple ci-dessous, count
et object
sont des propriétés de premier niveau, mais object.id
ne l'est pas :
js
const count = ref(0)
const object = { id: ref(0) }
Cette expression fonctionne donc comme prévu :
template
{{ count + 1 }}
...alors que celui-ci ne le fait PAS :
template
{{ object.id + 1 }}
Le résultat rendu sera [object Object]1
car object.id
n'est pas déballé lors de l'évaluation de l'expression et reste un objet ref. Pour résoudre ce problème, nous pouvons déstructurer id
en une propriété de premier niveau :
js
const { id } = object
template
{{ id + 1 }}
Désormais le résultat rendu sera 2
.
Une autre chose à noter est qu'une ref est déballée s'il s'agit de la valeur finale évaluée d'une interpolation de texte (c'est-à-dire une balise {{ }}
), donc ce qui suit rendra 1
:
template
{{ object.id }}
Il s'agit d'une fonctionnalité de commodité de l'interpolation de texte et est équivalent à {{ object.id.value }}
.