Adloun

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 ref et reactive, 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 watch et watchEffect : 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 Proxy qui 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

Illustration

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 : « »
Définition 3.1Objet réactif

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 cours

En accès libre, sans inscription.

Exercices corrigés de ce chapitre

Lire la suite du cours →