Retour au cours

frontend / vuejs

Lifecycle hooks

Leçon 71 exercice

Explication

Ce que vous allez apprendre

  • Identifier les moments clés du cycle de vie d'un composant, de sa création à sa destruction
  • Savoir dans quel hook initialiser une ressource qui nécessite le DOM
  • Nettoyer systématiquement toute ressource créée manuellement pour éviter les fuites mémoire
  • Créer une "error boundary" avec onErrorCaptured pour isoler les erreurs d'un sous-arbre
  • Comprendre la particularité des hooks onActivated/onDeactivated liés à <KeepAlive>

Dans quel contexte ?

Un composant affiche la largeur actuelle de la fenêtre du navigateur, mise à jour en temps réel lors d'un redimensionnement. Il doit s'abonner à l'événement resize du navigateur au bon moment (quand le composant existe réellement), et surtout se désabonner quand le composant est détruit — sinon, chaque montage/démontage successif de ce composant accumulerait des écouteurs fantômes qui ne seront jamais nettoyés.

D'abord, un composant traverse des étapes précises et prévisibles

Un composant Vue naît, existe un certain temps dans le DOM, se met potentiellement à jour plusieurs fois suite à des changements réactifs, puis finit par être détruit (navigation vers une autre page, condition v-if devenue fausse). Chaque étape de ce cycle expose un hook dédié qu'on peut utiliser pour exécuter du code au bon moment précis.

HookMomentLe DOM est-il accessible ?
onBeforeMountJuste avant le premier renduNon
onMountedJuste après le premier renduOui
onBeforeUnmountJuste avant la destructionOui (dernière chance)
onUnmountedJuste après la destructionNon

Prérequis

Cette leçon suppose une bonne maîtrise de <script setup> et de ref() (leçons 1 et 3), puisque les exemples combinent hooks de cycle de vie et état réactif.

Ensuite, le hook le plus utilisé au quotidien

onMounted est le point d'entrée pour tout ce qui nécessite que le DOM existe réellement : mesurer un élément, initialiser une bibliothèque tierce qui manipule le DOM, démarrer un minuteur. C'est l'équivalent, en esprit, du moment où une page web devient réellement interactive.

Il reste une règle absolue à respecter : toujours nettoyer

Toute ressource créée manuellement dans onMounted (un setInterval, un écouteur d'événement, un abonnement à un service externe) DOIT être nettoyée dans onUnmounted. Sans ce nettoyage symétrique, chaque montage/démontage du composant accumule des ressources jamais libérées — une fuite mémoire qui dégrade progressivement les performances d'une application à mesure que l'utilisateur navigue.

Piège dangereux

Un window.addEventListener('resize', ...) sans onUnmounted correspondant continue de référencer le composant même après sa destruction. Sur une application avec beaucoup de navigation, ce type d'oubli s'accumule silencieusement jusqu'à provoquer un ralentissement perceptible, souvent difficile à diagnostiquer sans outil de profilage mémoire.

Maintenant, isoler une erreur avant qu'elle ne casse toute l'application

onErrorCaptured intercepte une erreur survenue dans un composant descendant, permettant de l'afficher proprement plutôt que de laisser toute l'application planter. Retourner false depuis ce hook empêche l'erreur de continuer à se propager vers des composants parents encore plus haut dans l'arbre.

Astuce

Place un onErrorCaptured au niveau d'une section fonctionnelle isolée de l'application (comme un widget tiers ou une fonctionnalité expérimentale) plutôt qu'à la racine : ça évite qu'un bug localisé ne fasse planter l'intégralité de l'interface pour l'utilisateur.

Enfin, un cas particulier lié à la mise en cache de composants

onActivated/onDeactivated ne concernent que les composants gardés en vie par <KeepAlive> (vu en détail dans une leçon ultérieure) : contrairement à onMounted/onUnmounted, l'instance du composant n'est jamais réellement détruite, seulement masquée puis réaffichée.

Maintenant que le cycle de vie d'un composant est clair, la prochaine leçon explore comment des composants communiquent du contenu, pas seulement des données : les slots.

Commandes & code

Lifecycle hooks

Chaque composant traverse un cycle de vie : création, montage, mise à jour, démontage.

vue
<script setup>
import {
  onBeforeMount, onMounted,
  onBeforeUpdate, onUpdated,
  onBeforeUnmount, onUnmounted,
  onErrorCaptured,
  ref,
} from 'vue'

const compteur = ref(0)
let intervalleId = null

onBeforeMount(() => {
  console.log('avant montage : le DOM du composant n\'existe pas encore')
})

onMounted(() => {
  console.log('monté : le DOM est accessible, idéal pour mesurer, initialiser une lib externe...')
  intervalleId = setInterval(() => compteur.value++, 1000)
})

onBeforeUpdate(() => {
  console.log('avant mise à jour du DOM suite à un changement réactif')
})

onUpdated(() => {
  console.log('DOM mis à jour : à utiliser avec parcimonie, risque de boucle si on modifie l\'état ici')
})

onBeforeUnmount(() => {
  console.log('juste avant destruction : dernière chance d\'accéder au DOM')
})

onUnmounted(() => {
  // Nettoyage OBLIGATOIRE des ressources créées manuellement (timers, listeners, souscriptions)
  clearInterval(intervalleId)
  console.log('composant détruit, ressources libérées')
})

onErrorCaptured((erreur, instance, info) => {
  console.error('erreur capturée depuis un composant descendant :', erreur, info)
  return false // false = empêche la propagation vers les parents supérieurs
})
</script>

<template>
  <p>{{ compteur }}</p>
</template>
js
// Correspondance avec les hooks Options API (pour ceux qui migrent un code legacy)
// Options API        Composition API
// beforeCreate    -> setup() lui-même (exécuté avant tout)
// created         -> setup() lui-même
// beforeMount     -> onBeforeMount
// mounted         -> onMounted
// beforeUpdate    -> onBeforeUpdate
// updated         -> onUpdated
// beforeUnmount   -> onBeforeUnmount
// unmounted       -> onUnmounted
// errorCaptured   -> onErrorCaptured
vue
<!-- Cas pratique : abonnement à un événement DOM global, avec nettoyage propre -->
<script setup>
import { ref, onMounted, onUnmounted } from 'vue'

const largeurFenetre = ref(window.innerWidth)

function surRedimensionnement() {
  largeurFenetre.value = window.innerWidth
}

onMounted(() => window.addEventListener('resize', surRedimensionnement))
onUnmounted(() => window.removeEventListener('resize', surRedimensionnement))
// Sans ce onUnmounted : fuite mémoire si le composant est monté/démonté plusieurs fois
// (le listener continuerait de référencer un composant détruit).
</script>

<template>
  <p>Largeur de fenêtre : {{ largeurFenetre }}px</p>
</template>
js
// Hooks spécifiques au composant <KeepAlive> (composants mis en cache, pas détruits)
import { onActivated, onDeactivated } from 'vue'

onActivated(() => {
  console.log('composant réaffiché depuis le cache (pas de re-création)')
})
onDeactivated(() => {
  console.log('composant mis en cache, DOM détaché mais instance conservée')
})

Résumé

  • onMounted : initialisations nécessitant le DOM (mesures, librairies tierces, timers).
  • onUnmounted : nettoyage systématique de tout ce qui a été créé manuellement (timers, listeners, souscriptions).
  • onErrorCaptured permet de créer des "error boundaries" pour isoler les erreurs d'un sous-arbre.
  • onActivated/onDeactivated sont spécifiques aux composants gardés en vie par <KeepAlive>.

Exercices pratiques

1 disponible
1

Mission : traquer une fuite mémoire sur un widget météo

Objectif : Diagnostiquer une ressource jamais nettoyée dans un composant remonté plusieurs fois, puis corriger avec les hooks adaptés.

Contexte

Un widget WidgetMeteo s'affiche seulement quand l'utilisateur ouvre un panneau latéral (v-if="panneauOuvert"), donc il est monté et démonté à répétition. Dans onMounted, il exécute setInterval(actualiserMeteo, 5000) pour rafraîchir la météo toutes les 5 secondes, mais aucun onUnmounted n'a été ajouté. Après avoir ouvert et fermé le panneau une dizaine de fois, l'onglet du navigateur devient visiblement plus lent et la console affiche des appels API en rafale.

Résoudre l’exercice →