La réactivité
Cours · Vue : construire une application web (école d'ingénieur), chapitre 3 · école d'ingénieur, informatique · le début du chapitre, la suite sur Adloun
Le catalogue du chapitre précédent compte les articles du panier, mais ne sait pas lesquels. Un vrai panier est une liste de lignes (un livre, une quantité), d'où se déduisent le nombre d'articles de l'en-tête, le total, la disponibilité de chaque livre. Ces valeurs dérivées ne doivent jamais être recopiées à la main : elles se calculent à partir de l'état. Et certaines actions doivent suivre un changement : enregistrer le panier, lancer une recherche quand le visiteur a fini de taper.
Ce chapitre présente les outils de la réactivité de Vue : ref en détail, reactive, les propriétés calculées computed, les observateurs watch et watchEffect. Il explique quand Vue met la page à jour, et comment il sait quoi mettre à jour, en reconstruisant un petit moteur de réactivité de vingt lignes.
Objectifs du chapitre
- Choisir entre
refetreactive, et éviter la perte de réactivité par déstructuration. - Déduire une valeur de l'état avec
computed, et expliquer en quoi elle diffère d'une fonction. - Réagir à un changement avec
watchetwatchEffect: sources, ancienne valeur, exécution immédiate, nettoyage. - Prévoir le moment d'un rendu, et attendre la mise à jour du DOM avec
nextTick. - Expliquer le principe de la réactivité de Vue : un
Proxyqui suit les lectures et déclenche les effets aux écritures.
Les affichages de ce chapitre ont été produits dans Chromium, avec Vue 3.5.43, en simulant les clics décrits avec l'outil Playwright ; ceux du moteur miniature, avec Node.js.
3.1 ref en détail
Une ref ne contient pas seulement des nombres ou des chaînes. Si sa valeur est un objet ou un tableau, Vue rend cet objet réactif en profondeur : les méthodes du tableau, les propriétés des objets qu'il contient, tout est suivi.
<div id="app">
<p>{{ lignes.length }} ligne(s) : {{ lignes.map((l) => l.titre + " × " + l.quantite).join(", ") }}</p>
<button type="button" @click="ajouter">Ajouter Java</button>
<button type="button" @click="doubler">Doubler la première ligne</button>
</div>
const { createApp, ref, isRef } = Vue;
createApp({
setup() {
const lignes = ref([{ titre: "Analyse", quantite: 1 }]);
console.log(isRef(lignes), Array.isArray(lignes.value), lignes.value.length);
function ajouter() {
lignes.value.push({ titre: "Java", quantite: 1 }); // une méthode du tableau
}
function doubler() {
lignes.value[0].quantite *= 2; // une propriété d'un objet du tableau
}
return { lignes, ajouter, doubler };
},
}).mount("#app");
La console, puis le paragraphe au chargement, après « Ajouter Java » et après « Doubler la première ligne » :
true true 1
1 ligne(s) : Analyse × 1
2 ligne(s) : Analyse × 1, Java × 1
2 ligne(s) : Analyse × 2, Java × 1

isRef confirme que lignes est une ref ; sa valeur est un vrai tableau. On peut le modifier sur place (push, splice, sort, une affectation à un élément) ou le remplacer entièrement (lignes.value = …) : Vue suit les deux.
3.2 reactive
Pour un objet, Vue propose aussi reactive, qui renvoie directement un objet réactif, sans .value.
<div id="app">
<p>Code promo : « {{ panier.code }} » ; copie : « {{ code }} »</p>
<button type="button" @click="panier.code = 'RENTREE'">Saisir RENTREE</button>
</div>
const { createApp, reactive, isReactive } = Vue;
createApp({
setup() {
const brut = { lignes: [], code: "" };
const panier = reactive(brut);
console.log(panier === brut, isReactive(panier), isReactive(brut));
console.log(isReactive(panier.lignes)); // la réactivité est profonde
const { code } = panier; // une COPIE de la valeur actuelle : ""
return { panier, code };
},
}).mount("#app");
La console, puis le paragraphe avant et après le clic :
false true false
true
Code promo : « » ; copie : « »
Code promo : « RENTREE » ; copie : « »
reactive(objet) renvoie un proxy de l'objet : un nouvel objet qui se comporte comme l'original (il en lit et écrit les propriétés) mais signale à Vue chaque lecture et chaque écriture. L'objet d'origine, lui, n'est pas réactif. La réactivité est profonde : les objets et tableaux contenus sont à leur tour enveloppés quand on les lit.
La sortie montre l'inconvénient de reactive : la copie code, obtenue par déstructuration, n'a pas suivi. Déstructurer, c'est lire la propriété une fois et ranger sa valeur, une simple chaîne, dans une variable : le lien avec l'objet est perdu. Pour garder ce lien, toRefs(panier) renvoie un objet de refs, chacune reliée à une propriété :
const { createApp, reactive, toRefs } = Vue;
createApp({
setup() {
const panier = reactive({ lignes: [], code: "" });
const { code } = toRefs(panier); // une ref reliée à panier.code
return { panier, code };
},
}).mount("#app");
Après le clic :
Code promo : « RENTREE » ; ref liée : « RENTREE »
ref ou reactive ?
reactive ne convient qu'aux objets (pas à un nombre ni à une chaîne), perd sa réactivité quand on le déstructure, et ne se remplace pas : réaffecter la variable qui le contient coupe le lien avec le gabarit (exercice 3.9). ref accepte toute valeur, se transmet sans perte, se remplace avec .value =. La documentation de Vue recommande ref comme outil par défaut ; c'est le choix de ce livre. On rencontre reactive pour un objet d'état groupé, comme un formulaire (chapitre 4).
3.3 Les propriétés calculées
Le total d'un panier se déduit des lignes. On pourrait l'écrire comme une fonction appelée par le gabarit, ce que faisait le catalogue du chapitre précédent ; Vue propose mieux.
<div id="app">
<p>Méthode : {{ totalMethode() }} € ; computed : {{ total }} €</p>
<p>Clics sans rapport : {{ autre }}</p>
<button type="button" @click="autre++">Autre chose</button>
<button type="button" @click="lignes[0].quantite++">Un Java de plus</button>
</div>
const { createApp, ref, computed } = Vue;
createApp({
setup() {
const lignes = ref([
{ titre: "Java", prix: 32, quantite: 1 },
{ titre: "Petit Prince", prix: 8.5, quantite: 2 },
]);
const autre = ref(0);
function somme() {
return lignes.value.reduce((s, l) => s + l.prix * l.quantite, 0);
}
function totalMethode() {
console.log("calcul par la méthode");
return somme();
}
const total = computed(() => {
console.log("calcul par computed");
return somme();
});
return { lignes, autre, total, totalMethode };
},
}).mount("#app");
La suite du cours est sur Adloun
Le chapitre en entier, formules et figures comprises, avec le tuteur, les fiches, les QCM et les exercices.
Lire la suite du coursEn accès libre, sans inscription.