Contactez-nous

`React.memo` : Mémoïsation des composants fonctionnels

Apprenez à utiliser React.memo pour optimiser vos composants fonctionnels React en empêchant les re-rendus inutiles lorsque les props n'ont pas changé.

Le problème : Re-rendus inutiles des composants enfants

Par défaut, dans React, lorsqu'un composant parent se re-rend (suite à un changement d'état, de contexte ou de ses propres props), il déclenche également le re-rendu de tous ses composants enfants directs, même si les props passées à ces enfants n'ont pas réellement changé de valeur. React doit rendre l'enfant pour pouvoir comparer le nouveau résultat de rendu avec le précédent lors de la phase de réconciliation.

Si le composant enfant est coûteux à rendre (contient une logique complexe, effectue des calculs intensifs, ou a lui-même de nombreux descendants), ce re-rendu peut être superflu et gaspiller des ressources s'il produit exactement le même résultat que lors du rendu précédent parce que ses props n'ont pas changé.

C'est précisément ce problème que `React.memo` vise à résoudre pour les composants fonctionnels. Il permet d'"envelopper" un composant fonctionnel et d'indiquer à React qu'il ne doit le re-rendre que si ses props ont effectivement changé.

Qu'est-ce que `React.memo` et comment ça marche ?

React.memo est un Higher-Order Component (HOC). C'est une fonction qui prend un composant fonctionnel en argument et retourne un nouveau composant mémoïsé.

Le composant mémoïsé retourné par `React.memo` fonctionne de la manière suivante : avant de se re-rendre, il effectue une comparaison superficielle (shallow comparison) de ses nouvelles props avec ses props précédentes. Si la comparaison détermine que les props sont identiques, React saute le re-rendu de ce composant (et donc de toute sa sous-arborescence) et réutilise le résultat du rendu précédent. Si les props sont différentes, le composant se re-rend normalement.

La comparaison superficielle fonctionne ainsi :

  • Pour les props de types primitifs (chaînes, nombres, booléens, `null`, `undefined`), elle compare les valeurs directement (ex: `5 === 5` est vrai).
  • Pour les props de types complexes (objets, tableaux, fonctions), elle compare les références (ex: `{}` est différent de `{}` car ce sont deux objets distincts en mémoire, même s'ils sont vides).

Cette distinction sur la comparaison des références pour les objets et fonctions est cruciale et souvent la source de confusion ou d'échec de la mémoïsation avec `React.memo`.

Utilisation basique de `React.memo`

L'utilisation de base est très simple : vous enveloppez l'exportation de votre composant fonctionnel avec `React.memo`.

import React, { useState } from 'react';

// Composant enfant potentiellement coûteux à rendre
const MonComposantEnfant = React.memo(function MonComposantEnfant({ nom, details }) {
  console.log(`Rendu de MonComposantEnfant pour ${nom}`);
  // Simule un rendu coûteux
  let i = 0; while (i < 100000000) { i++; }

  return (
    

Enfant : {nom}

Détails: {details.info}

); }); // Composant parent function MonComposantParent() { const [compteur, setCompteur] = useState(0); const [autreEtat, setAutreEtat] = useState(0); console.log('Rendu de MonComposantParent'); // !! ATTENTION: details est recréé à chaque rendu du parent const detailsEnfant = { info: 'Information stable' }; return (

Parent

Compteur (non lié à l'enfant): {compteur}


); } export default MonComposantParent;

Dans cet exemple, `MonComposantEnfant` est enveloppé par `React.memo`. La prop `nom` est une chaîne primitive et ne change pas. Cependant, la prop `details` est un objet littéral créé à chaque rendu de `MonComposantParent`. Même si son contenu (`info`) ne change pas, sa référence change à chaque fois. A cause de cela, la comparaison superficielle de `React.memo` échouera pour la prop `details`, et `MonComposantEnfant` se re-rendra à chaque fois que le parent se re-rend (quand on clique sur le bouton), annulant le bénéfice de `React.memo` dans ce cas précis.

Gérer les props non primitives (objets, fonctions)

Pour que `React.memo` fonctionne efficacement lorsque vous passez des objets, des tableaux ou des fonctions comme props, vous devez vous assurer que ces props conservent la même référence entre les rendus du parent si leur contenu ou leur comportement n'a pas changé.

C'est là que les hooks `useMemo` (pour les objets/tableaux) et `useCallback` (pour les fonctions) deviennent indispensables dans le composant parent :

import React, { useState, useMemo, useCallback } from 'react';

const MonComposantEnfant = React.memo(function MonComposantEnfant({ nom, details, onClick }) {
  console.log(`Rendu de MonComposantEnfant pour ${nom}`);
  // ... (rendu coûteux simulé)
  return (
    

Enfant : {nom}

Détails: {details.info}

); }); function MonComposantParentOptimise() { const [compteur, setCompteur] = useState(0); const [autreEtat, setAutreEtat] = useState('stable'); // Etat utilisé par useCallback console.log('Rendu de MonComposantParentOptimise'); // 1. Mémoïser l'objet 'details' avec useMemo // Ne sera recalculé que si 'autreEtat' change (exemple de dépendance) const detailsEnfant = useMemo(() => ({ info: `Information basée sur ${autreEtat}` }), [autreEtat]); // 2. Mémoïser la fonction 'handleClick' avec useCallback // Ne sera recréée que si 'autreEtat' change const handleClick = useCallback(() => { console.log(`Clic sur l'enfant avec l'état : ${autreEtat}`); }, [autreEtat]); return (

Parent Optimisé

Compteur (non lié à l'enfant): {compteur}


); } export default MonComposantParentOptimise;

Maintenant, tant que `autreEtat` ne change pas, `detailsEnfant` et `handleClick` conserveront la même référence entre les rendus de `MonComposantParentOptimise`. Lorsque seul `compteur` change, `React.memo` pourra effectivement comparer les props de `MonComposantEnfant`, constater qu'elles n'ont pas changé de référence, et sauter le re-rendu coûteux de l'enfant.

Utilisation d'une fonction de comparaison personnalisée

Dans de rares cas, la comparaison superficielle par défaut n'est pas suffisante. Vous pourriez avoir des props complexes où un changement de référence ne signifie pas nécessairement un besoin de re-rendu, ou inversement. `React.memo` accepte une deuxième fonction optionnelle comme argument, souvent appelée `areEqual`, qui reçoit les anciennes props et les nouvelles props et doit retourner `true` si les props sont considérées comme égales (ce qui empêche le re-rendu), et `false` sinon.

function propsSontEgales(prevProps, nextProps) {
  // Logique de comparaison personnalisée complexe
  // Retourne true si on veut éviter le re-rendu, false sinon
  return prevProps.utilisateur.id === nextProps.utilisateur.id && 
         prevProps.config.theme === nextProps.config.theme;
}

const MonComposantSpecifique = React.memo(ComposantOriginal, propsSontEgales);

L'utilisation d'une fonction de comparaison personnalisée doit être mûrement réfléchie car elle peut devenir complexe à maintenir et annuler les bénéfices de performance si elle est mal écrite.

Quand utiliser `React.memo` ?

React.memo est une optimisation, pas une solution miracle. Elle introduit une complexité supplémentaire (la comparaison des props) et un coût de mémoire (pour stocker le résultat précédent). Il ne faut donc pas l'appliquer aveuglément à tous les composants.

Utilisez `React.memo` de manière stratégique lorsque toutes les conditions suivantes (ou la plupart) sont réunies :

  • Le composant est un composant fonctionnel.
  • Le composant se re-rend fréquemment.
  • Le composant se re-rend souvent avec exactement les mêmes props.
  • Le composant est relativement coûteux à rendre (il a une UI complexe, beaucoup de descendants, ou effectue des calculs).
  • Vous avez confirmé via le Profiler React DevTools que les re-rendus de ce composant constituent un problème de performance.

Evitez l'optimisation prématurée. Mesurez d'abord, identifiez les vrais problèmes, puis appliquez `React.memo` (souvent en conjonction avec `useMemo` et `useCallback` dans le parent) là où c'est le plus bénéfique.

En résumé, `React.memo` est un outil puissant pour optimiser les composants fonctionnels en leur permettant de sauter des re-rendus lorsque leurs props n'ont pas changé, améliorant ainsi les performances globales de votre application React.