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
Nous pouvons créer un objet ou un tableau réactif avec la fonction reactive()
:
js
import { reactive } from 'vue'
const state = reactive({ count: 0 })
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. Si vous êtes curieux de connaître les détails, nous expliquons comment fonctionne le système de réactivité de Vue dans Reactivity in Depth - mais nous vous recommandons de le lire après avoir terminé le guide principal.
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 { reactive } from 'vue'
export default {
// `setup` est un hook spécial conçu pour la Composition API.
setup() {
const state = reactive({ count: 0 })
// expose l'état au template
return {
state
}
}
}
template
<div>{{ state.count }}</div>
De la même manière, nous pouvons déclarer des fonctions qui modifient l'état réactif dans la même portée et les exposer en tant que méthodes au côté de l'état :
js
import { reactive } from 'vue'
export default {
setup() {
const state = reactive({ count: 0 })
function increment() {
state.count++
}
// n'oubliez pas d'également exposer la fonction.
return {
state,
increment
}
}
}
Les méthodes exposées sont généralement utilisées comme écouteurs d'événements :
template
<button @click="increment">
{{ state.count }}
</button>
<script setup>
Exposer manuellement l'état et les méthodes via setup()
peut être verbeux. Heureusement, cela n'est nécessaire que lorsqu'on n'utilise pas d'outil de build. Lorsque l'on utilise des composants monofichiers, nous pouvons grandement simplifier l'utilisation avec <script setup>
:
vue
<script setup>
import { reactive } from 'vue'
const state = reactive({ count: 0 })
function increment() {
state.count++
}
</script>
<template>
<button @click="increment">
{{ state.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.
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.
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'
function increment() {
state.count++
nextTick(() => {
// accès au DOM mis à jour
})
}
Réactivité profonde
Dans Vue, l'état est profondément réactif par défaut. Cela signifie que vous pouvez vous attendre à ce que les changements soient détectés même lorsque vous modifiez des objets ou des tableaux imbriqués :
js
import { reactive } from 'vue'
const obj = reactive({
nested: { count: 0 },
arr: ['foo', 'bar']
})
function mutateDeeply() {
// cela va fonctionner comme prévu.
obj.nested.count++
obj.arr.push('baz')
}
Il est également possible de créer de manière explicite des objets partiellement réactifs où la réactivité n'est traquée qu'au niveau de la racine, mais ces objets ne sont généralement nécessaires que dans des cas d'utilisation avancée.
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 :
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
.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 })
Cela signifie également que lorsque nous assignons ou déstructurons la propriété d'un objet réactif à des variables locales, ou lorsque nous passons cette propriété dans une fonction, nous perdons la connexion de réactivité :
jsconst state = reactive({ count: 0 }) // n est une variable locale qui est déconnectée // de state.count. let n = state.count // n'affecte pas l'état original n++ // count est aussi déconnecté de state.count. 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 callSomeFunction(state.count)
Variables réactives avec ref()
Pour pallier aux limites de reactive()
, Vue fournit également une fonction ref()
qui nous permet de créer des "refs" réactives pouvant contenir n'importe quel type de valeur :
js
import { ref } from 'vue'
const count = ref(0)
ref()
prend l'argument et le retourne 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 refs
De la même manière que pour les propriétés d'un objet réactif, la propriété .value
d'une ref est réactive. De plus, lorsqu'elle contient des types d'objets, la ref convertit automatiquement sa .value
avec reactive()
.
Une ref contenant une valeur d'objet peut remplacer l'objet entier de manière réactive :
js
const objectRef = ref({ count: 0 })
// cela fonctionne de manière réactive
objectRef.value = { count: 1 }
Les refs peuvent également être passées à des fonctions ou déstructurées à partir d'objets simples sans perdre leur réactivité :
js
const obj = {
foo: ref(1),
bar: ref(2)
}
// la fonction reçoit une ref
// elle doit accéder à la valeur via .value mais elle
// va conserver la connexion de réactivité
callSomeFunction(obj.foo)
// toujours reactive
const { foo, bar } = obj
En d'autres termes, ref()
nous permet de créer une "référence" à n'importe quelle valeur et de la faire circuler sans perdre la réactivité. Cette capacité est très importante car elle est fréquemment utilisée pour extraire la logique dans les fonctions composables.
Déballage d'une ref dans les templates
Lorsque les refs sont accédées en tant que propriétés de premier niveau dans le template, elles sont automatiquement "déballées", il n'y a donc pas besoin d'utiliser .value
. Voici l'exemple précédent du compteur, en utilisant ref()
à la place :
vue
<script setup>
import { ref } from 'vue'
const count = ref(0)
function increment() {
count.value++
}
</script>
<template>
<button @click="increment">
{{ count }} <!-- pas besoin de .value -->
</button>
</template>
Notez que le déballage ne s'applique que si la référence est une propriété de premier niveau dans le contexte du rendu du template. Par exemple, foo
est une propriété de premier niveau, mais object.foo
ne l'est pas.
Ainsi, étant donné l'objet suivant :
js
const object = { foo: ref(1) }
L'expression suivante ne fonctionnera PAS comme prévu :
template
{{ object.foo + 1 }}
Le résultat rendu sera [object Object]
car object.foo
est un objet ref. Nous pouvons corriger cela en faisant de foo
une propriété de premier niveau :
js
const { foo } = object
template
{{ foo + 1 }}
Désormais le résultat rendu sera 2
.
Une chose à noter est qu'une ref sera également déballée si elle est la valeur finale évaluée d'une interpolation de texte (par exemple une balise {{ }}
), donc ce qui suit rendra 1
:
template
{{ object.foo }}
Il s'agit simplement d'une fonctionnalité pratique de l'interpolation de texte et elle est équivalente à {{ objet.foo.value }}
.
Déballage d'une ref dans des objets réactifs
Lorsqu'une ref
est accédée ou mutée en tant que propriété d'un objet réactif, elle est également automatiquement déballée, donc 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 désormais déconnectée de state.count
console.log(count.value) // 1
Le déballage des refs ne se produit que lorsqu'elles sont imbriquées dans un objet réactif profond. Il ne s'applique pas lorsqu'on y accède en tant que propriété d'un objet partiellement réactif.
Déballage d'une ref dans les tableaux et les collections
Contrairement aux objets réactifs, aucun déballage n'est 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')])
// besoin de .value
console.log(books[0].value)
const map = reactive(new Map([['count', ref(0)]]))
// besoin de .value
console.log(map.get('count').value)
Reactivity Transform
Devoir utiliser .value
avec les refs est un inconvénient imposé par les contraintes du langage JavaScript. Cependant, grâce aux transformations à la compilation nous pouvons améliorer l'ergonomie en ajoutant automatiquement .value
aux endroits appropriés. Vue fournit une transformation compilatoire qui nous permet d'écrire l'exemple précédent du "compteur" comme ceci :
vue
<script setup>
let count = $ref(0)
function increment() {
// pas besoin de .value
count++
}
</script>
<template>
<button @click="increment">{{ count }}</button>
</template>
Vous pouvez en savoir plus sur Reactivity Transform dans la section qui lui est consacrée. Notez qu'elle est actuellement encore expérimentale et peut être modifiée avant d'être finalisée.