Menu

React.memo : éviter un rendu quand les props sont égales

memo enveloppe un composant pour que React saute son nouveau rendu quand ses props sont superficiellement égales à la fois précédente. Découvrez quand il aide, pourquoi de nouveaux objets et de nouvelles fonctions dans les props l'annulent, et ce qu'il n'empêche pas.

Cette page contient des éditeurs exécutables - modifiez, exécutez et voyez la sortie instantanément.

memo permet à un composant de sauter son nouveau rendu quand ses props sont les mêmes que la fois précédente. Enveloppez un composant dans memo(...) et, quand son parent fait son rendu, React compare d'abord chaque nouvelle prop à l'ancienne ; si toutes sont égales, il réutilise le résultat précédent au lieu d'appeler à nouveau le composant.

Tapez dans le champ : il suit sans ralentir, et la console n'affiche render SlowList qu'une seule fois, lors du premier rendu. Retirez l'enveloppe memo( (et sa parenthèse fermante) et tapez à nouveau : chaque frappe attend maintenant 150 ms la liste et affiche un rendu.

La syntaxe

import { memo } from 'react';

const MemoizedComponent = memo(SomeComponent, arePropsEqual?);
  • SomeComponent est n'importe quel composant fonction. Le style habituel consiste à envelopper directement la définition : const Card = memo(function Card(props) { ... }). Nommer la fonction interne garde le nom dans les React DevTools et dans les messages d'erreur.
  • arePropsEqual est facultatif et rarement nécessaire (voir plus bas).
  • Le résultat est un composant que vous affichez comme n'importe quel autre.

Par défaut, React compare les anciennes et les nouvelles props de façon superficielle : il vérifie chaque prop avec Object.is. Les chaînes, les nombres et les booléens se comparent par valeur, donc title="Hi" est égal à title="Hi". Les objets, les tableaux et les fonctions se comparent par référence : deux objets au contenu identique restent des objets différents.

Dans l'exemple ci-dessus, items est déclaré hors du composant, c'est donc le même tableau à chaque rendu, et la comparaison réussit.

Les nouveaux objets et fonctions annulent memo

Tout ce qui est écrit sous forme de littéral dans le corps du parent est recréé à chaque rendu : { name: 'Ada' }, [1, 2, 3], () => save(). Passez-en un à un composant memo et la prop n'est jamais égale à la précédente.

Chaque clic affiche render Badge object prop mais jamais la version chaîne. Il existe trois façons de corriger le cas de l'objet :

  • Passer des primitives. name="Ada" au lieu de user={{ name: 'Ada' }} quand l'enfant n'a besoin que d'un ou deux champs.
  • Sortir les constantes du composant. Un objet qui ne change jamais peut vivre au niveau du module, comme items dans le premier exemple.
  • Le mettre en cache. Enveloppez les objets et les tableaux construits à partir des props ou de l'état dans useMemo, et les fonctions dans useCallback.

children pose le même problème. <Card><p>Hello</p></Card> crée un nouvel objet élément <p> à chaque rendu, donc un Card memo qui reçoit des children refait son rendu à chaque fois.

Ce que memo n'empêche pas

memo ne regarde que les props venant du parent. Un composant mémoïsé refait quand même son rendu quand :

  • son propre état change, via son propre useState ou useReducer ;
  • un contexte qu'il lit change, via useContext.

Cliquez sur Unrelated et Panel est sauté. Changez de thème et il refait son rendu, car il lit le contexte. Cliquez sur Like et il refait son rendu, car son propre état a changé. C'est le comportement voulu : memo sert à éviter du travail quand rien de ce dont dépend le composant n'a changé, et le contexte et l'état font partie de ses dépendances.

Une fonction de comparaison sur mesure

Le second argument remplace la comparaison superficielle. Il reçoit les props précédentes et suivantes et renvoie true quand elles doivent être considérées comme égales, ce qui signifie que React saute le rendu.

const MemoChart = memo(Chart, (prev, next) => {
    return (
        prev.points.length === next.points.length &&
        prev.points.every((p, i) => p === next.points[i]) &&
        prev.color === next.color &&
        prev.onSelect === next.onSelect
    );
});

Vous en aurez rarement besoin, et il est facile de se tromper. Si la fonction ignore une prop, le composant continue d'afficher l'ancienne valeur de cette prop. Ignorer une prop fonction est le pire cas : le composant sauté continue d'appeler l'ancienne fonction, qui lit un ancien état. Comparez chaque prop, et ne faites pas de comparaisons profondes sur de grandes données, car la comparaison coûterait alors plus que le rendu qu'elle évite. Des props stables grâce à useMemo et useCallback sont presque toujours la meilleure solution.

Vérifier que memo fonctionne

La vérification la plus rapide est celle de cette page : un console.log en haut du composant. S'il s'affiche alors que vous attendiez un rendu sauté, affichez aussi les props et comparez-les aux précédentes pour trouver celle qui a changé. Dans un vrai projet, les React DevTools aident davantage : activez "Highlight updates when components render" dans leurs réglages pour voir quels composants clignotent à chaque interaction, et l'onglet Profiler enregistre chaque rendu et, avec le réglage correspondant activé, la raison du rendu de chaque composant.

Deux situations ressemblent à un échec de memo, sans en être :

  • Le premier rendu a toujours lieu. memo ne saute les rendus qu'une fois que le composant a fait un rendu avec ces props.
  • Les builds de développement peuvent faire plus de rendus. Sous StrictMode, React fait le rendu de chaque composant deux fois en développement pour détecter le code impur. L'aperçu fonctionne ici comme un build de production, donc chaque ligne affichée ci-dessus correspond à un vrai rendu.

Quand utiliser memo

memo en vaut la peine quand un composant est lent à afficher et que son parent refait souvent son rendu avec les mêmes props pour lui : une longue liste ou un graphique à côté d'un champ, une barre latérale dans une page qui se met à jour chaque seconde. Pour des composants qui s'affichent rapidement, ou dont les props changent de toute façon à chaque fois, il ajoute une comparaison et n'apporte rien. Avant de mémoïser, voyez si vous pouvez plutôt réorganiser, en descendant l'état ou en passant du JSX via children, comme le montre la page sur le rendu.

React Compiler 1.0, si votre projet l'utilise, mémoïse automatiquement les composants et leurs props au moment du build, ce qui rend inutiles la plupart des appels à memo écrits à la main. Sans lui, memo fonctionne exactement comme décrit ici et reste courant dans le code React.

Questions fréquentes

Que fait React.memo ?

Il renvoie une version mémoïsée d'un composant. Quand le parent fait son rendu, React compare les nouvelles props aux précédentes et saute le rendu du composant si chaque prop est égale selon Object.is.

Pourquoi mon composant memo refait-il encore son rendu ?

Généralement parce qu'une prop est un nouvel objet, un nouveau tableau ou une nouvelle fonction créé pendant le rendu du parent, et n'est donc jamais égale à la précédente. Le composant refait aussi son rendu quand son propre état change ou quand un contexte qu'il lit change, avec ou sans memo.

Quelle est la différence entre React.memo et useMemo ?

memo enveloppe un composant et saute son rendu. useMemo est un hook utilisé dans un composant pour mettre en cache une valeur calculée. Ils fonctionnent souvent ensemble : useMemo garde une prop objet stable pour que memo puisse sauter le rendu.

Faut-il envelopper chaque composant dans memo ?

Non. La plupart des rendus coûtent peu, et la comparaison des props a son propre coût. Utilisez-le pour des composants lents à afficher qui reçoivent souvent les mêmes props.

memo fonctionne-t-il avec children ?

Rarement. Le JSX passé dans children est un nouvel objet à chaque rendu, donc un composant memo qui reçoit des children refait presque toujours son rendu.

Illustration des langages de programmation de Coddy

Apprendre à coder avec Coddy

COMMENCER