La grande majorité des problèmes de performances React ne viennent pas de mauvais algorithmes, mais de rendus inutiles. Chaque fois qu'un composant se rend, il réexécute son sous-arbre ; c'est généralement peu coûteux, mais avec de grandes listes, des calculs lourds ou des arbres profonds, l'interface commence à saccader. Cet article montre comment mesurer les rendus et comment les réduire raisonnablement avec le trio memo, useMemo et useCallback.
Pourquoi React effectue-t-il un nouveau rendu ?
Un composant se re-rend dans trois cas : quand son propre state change, quand les props qu'il reçoit changent, ou quand son parent se rend. Ce dernier point est le plus souvent oublié : quand un parent se rend, ses enfants se re-rendent par défaut, même si leurs props n'ont jamais changé.
La plupart du temps, ce n'est pas un problème ; le rendu de React est une simple exécution JavaScript, et il ne touche au vrai DOM que s'il y a une réelle différence. L'ennui commence quand le rendu lui-même est coûteux : un tableau de milliers de lignes, un graphique complexe, ou une boucle lourde recalculée à chaque rendu.
Mesurer avant d'optimiser
L'optimisation prématurée est le plus grand piège. Avant de parsemer memo partout, mesurez où c'est réellement lent. L'onglet Profiler de l'extension React Developer Tools est fait exactement pour cela : lancez l'enregistrement, effectuez l'interaction, arrêtez. Le graphique en flammes montre quel composant s'est rendu combien de fois et en combien de millisecondes.
En développement, activez l'option « Record why each component rendered » du Profiler. Si vous voyez souvent la raison « Parent component rendered », vous avez trouvé un candidat à la mémoïsation.
React.memo pour mémoriser un composant
React.memo enveloppe un composant et saute le rendu si ses props n'ont pas changé selon une comparaison superficielle (shallow). C'est idéal pour les composants de présentation purs, pilotés par les props.
import { memo } from 'react';
const UserRow = memo(function UserRow({ name, email }) {
console.log('render', name);
return (
<tr>
<td>{name}</td>
<td>{email}</td>
</tr>
);
});
Désormais, UserRow ne se re-rend pas quand le parent se rend, tant que name et email restent identiques. Mais attention : la comparaison superficielle compare les props objets et fonctions par référence. Si vous créez un nouvel objet ou une nouvelle fonction à chaque rendu, memo ne sert à rien, car la référence change à chaque fois.
useMemo pour mettre en cache les calculs coûteux
useMemo mémorise une valeur : il ne réexécute pas le calcul tant que les dépendances que vous fournissez ne changent pas. Il a deux usages principaux : éviter des calculs réellement coûteux, et conserver une référence d'objet/tableau stable à transmettre aux enfants enveloppés par memo.
const sortedUsers = useMemo(() => {
return [...users].sort((a, b) => a.name.localeCompare(b.name));
}, [users]);
Ici, le tri ne s'exécute que lorsque le tableau users change ; quand un autre state du parent déclenche un rendu, le tri est sauté. Règle clé : soyez honnête avec le tableau de dépendances. Chaque variable externe utilisée dans le calcul doit y figurer, sinon vous travaillez avec des valeurs périmées (stale).
Ne cédez pas à la tentation de tout envelopper dans useMemo. La comparaison et le cache eux-mêmes ont un coût ; mémoïser un calcul bon marché comme a + b est une perte nette.
useCallback pour stabiliser une référence de fonction
useCallback est en réalité la forme spécifique aux fonctions de useMemo : il préserve la référence d'une fonction jusqu'à ce que les dépendances changent. Sa vraie valeur apparaît quand vous passez un callback à un enfant enveloppé par memo.
const handleDelete = useCallback((id) => {
setUsers((prev) => prev.filter((u) => u.id !== id));
}, []);
// l'enfant memo ne se re-rend plus à cause de cette prop
<UserRow user={user} onDelete={handleDelete} />
Avec un tableau de dépendances vide, la fonction est créée une seule fois au premier rendu et garde toujours la même référence. Remarquez que j'utilise la forme fonctionnelle (updater) de setUsers — ainsi j'évite d'ajouter users aux dépendances et je garde le callback stable.
Erreurs fréquentes
- Passer un nouvel objet littéral à un composant memo :
style={{ margin: 8 }}est une nouvelle référence à chaque rendu et annule la mémoïsation. Déplacez l'objet constant hors du composant ou créez-le avecuseMemo. - Utiliser l'index comme clé de liste : dans les listes avec tri ou insertion/suppression,
key={index}provoque de mauvaises correspondances et des rendus inutiles ; utilisez un id stable. - Parsemer l'optimisation partout :
memo/useMemoajoutés sans mesurer nuisent à la lisibilité sans apporter de bénéfice.
Une note sur React 19 et le compilateur
Le React Compiler introduit autour de React 19 vise à effectuer l'essentiel de cette mémoïsation automatiquement à la compilation ; autrement dit, le besoin futur d'écrire useMemo/useCallback à la main pourrait diminuer. Néanmoins, le compilateur n'est pas encore activé dans tous les projets, et comprendre ces concepts reste essentiel pour les bases de code existantes. Le principe « mesurer d'abord, réfléchir ensuite, optimiser en dernier » vaut pour chaque version.
Questions fréquentes
Quelle est la différence entre memo, useMemo et useCallback ?
memo enveloppe un composant et saute son rendu tant que les props ne changent pas. useMemo met en cache une valeur. useCallback garde stable une référence de fonction ; c'est en réalité un raccourci pour useMemo(() => fn, deps).
Dois-je envelopper chaque composant dans memo ?
Non. La comparaison a aussi un coût et constitue une perte nette pour les rendus bon marché. Ne mémoïsez que les composants dont vous avez prouvé avec le Profiler qu'ils sont réellement coûteux ou qu'ils se rendent très souvent.
useMemo accélère-t-il vraiment les choses ?
Seulement si le calcul est assez coûteux. Pour de l'arithmétique simple ou de petits tableaux, le coût du cache peut dépasser le bénéfice. Mesurez et décidez.
Vous voulez accélérer votre application React ? Pour profiler vos rendus et corriger les goulots d'étranglement avec de vraies mesures, contactez-moi.