Le cycle de vie d'un composant React comporte trois phases : le montage (le composant apparaît à l'écran pour la première fois), la mise à jour (il refait son rendu parce que son état, ses props ou son contexte ont changé) et le démontage (il est retiré). Dans les composants fonctions, vous réagissez à ces phases avec useEffect : un effet avec [] s'exécute après le montage, un effet avec des dépendances s'exécute après les mises à jour où elles ont changé, et la fonction renvoyée par un effet s'exécute au démontage.
La console sous l'aperçu montre chaque phase au moment où elle se produit.
La console commence par render, count = 0 et mounted. Chaque clic sur Count affiche un rendu et une mise à jour. Unmount affiche unmounted ; Mount again recommence depuis count = 0, car l'état a été jeté avec le composant.
Le montage
Un composant se monte la première fois que React fait son rendu à un endroit donné de l'arbre. React appelle la fonction, crée les nœuds DOM à partir du JSX qu'elle renvoie, les insère, laisse le navigateur afficher la page, puis exécute les effets du composant.
useEffect(fn, []) est l'endroit où démarrer ce qui doit exister tant que le composant est à l'écran : un abonnement, un minuteur, une connexion, une première requête de données. Il s'exécute après l'affichage, il ne retarde donc jamais la première image.
La mise à jour
Un composant se met à jour (refait son rendu) quand :
- son propre état change,
- son parent refait son rendu (par défaut, chaque enfant refait aussi le sien, que ses props aient changé ou non),
- un contexte qu'il lit change.
Après chaque mise à jour, React exécute les effets dont les dépendances ont changé. Avant de relancer un effet, il exécute le nettoyage précédent de cet effet, donc l'ancien abonnement est fermé avant que le nouveau s'ouvre. La page sur le rendu détaille ce qui déclenche un rendu.
Le démontage
Un composant se démonte quand son parent cesse de l'afficher (comme {show && <Counter />} le fait ci-dessus), quand sa key change, ou quand la racine elle-même est démontée. React retire ses nœuds DOM, exécute le nettoyage de chaque effet et jette son état. Si le composant réapparaît plus tard, c'est une instance toute neuve.
Parents et enfants
React fait le rendu de haut en bas mais exécute les effets de bas en haut. Quand un parent et un enfant se montent ensemble, les effets de l'enfant s'exécutent en premier, donc l'effet d'un parent peut compter sur des enfants entièrement mis en place.
Le montage affiche render Parent, render Child, Child effect, Parent effect. Appuyez sur Remove et les nettoyages s'exécutent dans l'ordre inverse : Parent cleanup, puis Child cleanup.
Phases de rendu et de commit
Chaque mise à jour passe par deux phases :
- Phase de rendu. React appelle vos composants pour calculer le nouveau JSX et le compare au résultat précédent. Rien ne change encore à l'écran. React peut mettre ce travail en pause, l'exécuter deux fois en StrictMode ou le jeter, c'est pourquoi le rendu doit être pur : pas de requêtes, pas d'abonnements, pas de modification de variables extérieures.
- Phase de commit. React applique les différences au DOM, attache les refs et exécute les callbacks de
useLayoutEffect, tout cela avant que le navigateur affiche la page. Les callbacks deuseEffects'exécutent ensuite, généralement une fois que le navigateur a affiché la page. Quand un clic ou une autre entrée discrète a causé la mise à jour, React les exécute plutôt juste après les effets de layout, pour que le résultat soit prêt avant l'événement suivant.
Chaque clic affiche les trois lignes dans le même ordre. Ces effets n'ont pas de tableau de dépendances, ils s'exécutent donc après chaque rendu. Ajoutez [] comme second argument aux deux et ils ne s'exécutent qu'après le premier.
Méthodes de cycle de vie des classes et leurs équivalents en hooks
Les composants classes détaillent le cycle de vie sous forme de méthodes nommées. Vous les rencontrerez dans du code ancien et dans les error boundaries, qui ont toujours besoin d'une classe.
| Méthode de classe | Quand elle s'exécute | Équivalent dans un composant fonction |
|---|---|---|
constructor | une fois, avant le premier rendu | useState(initial), useRef |
static getDerivedStateFromProps | avant chaque rendu | calculer la valeur pendant le rendu, ou réinitialiser avec une key |
render | à chaque rendu | le corps de la fonction |
componentDidMount | après l'insertion du premier rendu dans le DOM | useEffect(fn, []) |
shouldComponentUpdate | avant une mise à jour, peut la sauter | React.memo (il renvoie la réponse inverse : « les props sont-elles égales ») |
getSnapshotBeforeUpdate | juste avant la modification du DOM | pas de hook direct ; useLayoutEffect avec une ref est le plus proche |
componentDidUpdate | après chaque mise à jour | useEffect(fn, [deps]) (s'exécute aussi après le montage) |
componentWillUnmount | avant le retrait | le nettoyage renvoyé par useEffect |
getDerivedStateFromError, componentDidCatch | quand un enfant lève une erreur | aucun : écrivez une classe error boundary |
Voici une classe qui affiche chaque méthode au moment où elle s'exécute :
Le montage affiche constructor, getDerivedStateFromProps, render, componentDidMount. Un clic affiche getDerivedStateFromProps, shouldComponentUpdate, render, getSnapshotBeforeUpdate, componentDidUpdate. Faites renvoyer false à shouldComponentUpdate et les clics cessent de mettre à jour le bouton : l'état change, mais React saute le rendu. La page sur les composants classes montre comment convertir une classe comme celle-ci en fonction.
Pensez synchronisation, pas cycle de vie
Le tableau des hooks est utile pour lire du code ancien, mais il peut vous pousser à écrire des effets qui copient les habitudes des classes, comme un gros effet « monté » et un gros effet « mis à jour ». Un effet se lit mieux comme « garder cette chose extérieure synchronisée avec ces valeurs » : se connecter à roomId, et quand roomId change, se déconnecter puis se reconnecter. Écrivez un effet par chose à synchroniser, listez les valeurs qu'il utilise comme dépendances, et laissez React décider quand l'exécuter.
Questions fréquentes
Quelles sont les phases du cycle de vie d'un composant React ?
Le montage (React crée le composant et place son résultat à l'écran), la mise à jour (il refait son rendu parce que son état, ses props ou son contexte ont changé) et le démontage (il est retiré et fait son nettoyage).
Quel est l'équivalent en hook de componentDidMount ?
useEffect(() => { ... }, []). Le tableau de dépendances vide fait qu'il s'exécute une seule fois, après l'affichage du premier rendu. La fonction qu'il renvoie est l'équivalent de componentWillUnmount.
Quel est l'équivalent en hook de componentDidUpdate ?
useEffect avec les valeurs qui vous intéressent dans le tableau de dépendances, comme [userId]. Contrairement à componentDidUpdate, il s'exécute aussi après le premier rendu, il couvre donc à la fois le montage et la mise à jour.
Dans quel ordre s'exécutent les effets du parent et de l'enfant ?
React fait le rendu du parent vers le bas, mais exécute les effets des enfants vers le haut : l'effet d'un enfant s'exécute avant celui de son parent. Au démontage, le nettoyage du parent s'exécute avant celui de l'enfant.
Que sont les phases de rendu et de commit ?
Pendant la phase de rendu, React appelle vos composants pour déterminer à quoi l'écran doit ressembler ; il peut mettre ce travail en pause ou le jeter. Pendant la phase de commit, il applique les changements au DOM et exécute les effets de layout ; les effets ordinaires s'exécutent ensuite, généralement une fois que le navigateur a affiché la page.