frontend / vuejs
Lifecycle hooks
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
onErrorCapturedpour isoler les erreurs d'un sous-arbre - Comprendre la particularité des hooks
onActivated/onDeactivatedlié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.
| Hook | Moment | Le DOM est-il accessible ? |
|---|---|---|
onBeforeMount | Juste avant le premier rendu | Non |
onMounted | Juste après le premier rendu | Oui |
onBeforeUnmount | Juste avant la destruction | Oui (dernière chance) |
onUnmounted | Juste après la destruction | Non |
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.
<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>// 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<!-- 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>// 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).onErrorCapturedpermet de créer des "error boundaries" pour isoler les erreurs d'un sous-arbre.onActivated/onDeactivatedsont spécifiques aux composants gardés en vie par<KeepAlive>.
Exercices pratiques
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.