Cycle de vie et composables
Cours · Vue : construire une application web (école d'ingénieur), chapitre 8 · école d'ingénieur, informatique · le début du chapitre, la suite sur Adloun
La Librairie du chapitre précédent a deux défauts que tout visiteur remarquerait. Son panier disparaît dès qu'on recharge la page. Et ses livres sont écrits dans le code : pour en ajouter un, il faudrait reconstruire l'application, alors que le serveur de la Librairie, écrit au livre JavaScript, sait déjà les fournir. Pour corriger l'un et l'autre, il faut du code qui s'exécute à des moments précis de la vie d'un composant : charger les livres quand le catalogue apparaît, écouter le stockage du navigateur tant qu'il est affiché, cesser d'écouter quand il disparaît.
Ce chapitre présente ces moments, le cycle de vie d'un composant, et les fonctions qui permettent d'y accrocher du code. Il montre pourquoi tout ce qu'un composant démarre doit être arrêté, comment atteindre un élément du DOM, et surtout comment ranger ce code réutilisable dans des composables : des fonctions qui regroupent un état réactif et sa logique, et que n'importe quel composant peut appeler. À la fin, la Librairie charge ses livres depuis le serveur, en affichant un état de chargement et d'erreur, et son panier survit au rechargement de la page.
Objectifs du chapitre
- Connaître les étapes du cycle de vie d'un composant et l'ordre des crochets du parent et de ses enfants.
- Nettoyer tout ce qu'un composant a démarré (minuteurs, écouteurs, requêtes).
- Désigner un élément du gabarit avec une référence de gabarit.
- Écrire et utiliser des composables : conventions, paramètres réactifs, nettoyage.
- Charger des données depuis un serveur au travers du relais de Vite, avec états de chargement et d'erreur, et annulation des requêtes périmées.
Les exemples sont dans le projet Vite demos du chapitre, une page par exemple. Les sorties ont été produites dans Chromium, en simulant les actions décrites, avec le serveur de la Librairie lancé quand il le faut.
8.1 Le cycle de vie d'un composant
Un composant naît, s'affiche, se met à jour, puis disparaît. Vue permet d'enregistrer une fonction pour chacun de ces moments.
Les fonctions onBeforeMount, onMounted, onBeforeUpdate, onUpdated, onBeforeUnmount et onUnmounted, importées de vue, enregistrent une fonction appelée à l'étape correspondante du composant en cours de création. Elles s'appellent dans <script setup> (ou dans setup), pendant son exécution.
Voici un parent et un enfant qui annoncent chacune de leurs étapes ; le parent peut modifier la prop de l'enfant, puis le retirer.
<script setup>
import { onBeforeMount, onBeforeUnmount, onBeforeUpdate, onMounted, onUnmounted, onUpdated } from "vue";
defineProps({ titre: { type: String, required: true } });
console.log("Enfant : setup");
onBeforeMount(() => console.log("Enfant : onBeforeMount"));
onMounted(() => console.log("Enfant : onMounted"));
onBeforeUpdate(() => console.log("Enfant : onBeforeUpdate"));
onUpdated(() => console.log("Enfant : onUpdated"));
onBeforeUnmount(() => console.log("Enfant : onBeforeUnmount"));
onUnmounted(() => console.log("Enfant : onUnmounted"));
</script>
<script setup>
import { onBeforeMount, onBeforeUpdate, onMounted, onUpdated, ref } from "vue";
import EnfantCycle from "./EnfantCycle.vue";
const titre = ref("Java");
const visible = ref(true);
console.log("App : setup");
onBeforeMount(() => console.log("App : onBeforeMount"));
onMounted(() => console.log("App : onMounted"));
onBeforeUpdate(() => console.log("App : onBeforeUpdate"));
onUpdated(() => console.log("App : onUpdated"));
</script>
<template>
<button
type="button"
@click="titre += '!'"
>
Modifier le titre
</button>
<button
type="button"
@click="visible = !visible"
>
Afficher ou cacher l’enfant
</button>
<EnfantCycle
v-if="visible"
:titre="titre"
/>
</template>
--- chargement
App : setup
App : onBeforeMount
Enfant : setup
Enfant : onBeforeMount
Enfant : onMounted
App : onMounted
--- clic sur « Modifier le titre »
App : onBeforeUpdate
Enfant : onBeforeUpdate
Enfant : onUpdated
App : onUpdated
--- clic sur « Afficher ou cacher l’enfant »
App : onBeforeUpdate
Enfant : onBeforeUnmount
Enfant : onUnmounted
App : onUpdated
- Au montage, chaque composant exécute son
setupet sononBeforeMountavant ceux de ses enfants ; mais les enfants sont montés d'abord : leonMounteddu parent vient en dernier, quand tout son sous-arbre est dans le DOM. - À une mise à jour, le même emboîtement :
onBeforeUpdatedu parent, puis de l'enfant ;onUpdatedde l'enfant, puis du parent. - Le retrait d'un enfant (
v-ifdevenu faux) est une mise à jour du parent, pendant laquelle l'enfant passe paronBeforeUnmountetonUnmounted.
En pratique, deux crochets servent presque toujours. onMounted : le DOM du composant existe, on peut le lire ou y placer le focus, et ajouter des écouteurs à window ou document. onUnmounted : le composant a disparu, il faut arrêter tout ce qu'il a démarré. Le code d'initialisation qui n'a pas besoin du DOM (créer l'état, lancer une requête) s'écrit directement dans le script, sans crochet. Les crochets de mise à jour sont rarement utiles : pour réagir à une donnée, on utilise watch (chapitre 3). Enfin, sous <KeepAlive> (chapitre 7), un composant désactivé n'est pas démonté : onDeactivated et onActivated signalent sa mise de côté et son retour.
8.2 Nettoyer derrière soi
Un composant qui disparaît est retiré du DOM, et ses données réactives cessent d'être suivies. Mais ce qu'il a confié au navigateur continue : un minuteur continue de battre, un écouteur ajouté à window continue d'être appelé. Voici deux horloges, l'une sans nettoyage, l'autre avec.
<script setup>
import { ref } from "vue";
const tics = ref(0);
// Oubli : rien n'arrête ce minuteur quand le composant disparaît.
setInterval(() => {
tics.value += 1;
console.log("fuyante : tic", tics.value);
}, 1000);
</script>
<script setup>
import { onMounted, onUnmounted, ref } from "vue";
const tics = ref(0);
let minuteur = null;
onMounted(() => {
minuteur = setInterval(() => {
tics.value += 1;
console.log("propre : tic", tics.value);
}, 1000);
});
onUnmounted(() => clearInterval(minuteur));
</script>
On laisse battre un peu plus de deux secondes, puis on clique sur « Retirer les horloges », et l'on attend encore deux secondes :
fuyante : tic 1
propre : tic 1
fuyante : tic 2
propre : tic 2
fuyante : tic 3
propre : tic 3
--- horloges retirées
fuyante : tic 4
fuyante : tic 5
Les deux horloges ont disparu de la page, mais la première continue de battre, invisible, et de modifier une donnée que plus personne n'affiche. Si l'on affichait et retirait l'horloge cent fois (une fenêtre qu'on ouvre et ferme), cent minuteurs tourneraient : c'est une fuite, qui consomme de la mémoire et du processeur, et peut produire des effets étranges (une requête lancée par un composant disparu, un message affiché au mauvais moment).
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.