Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 45

Optimisation des performances React avec `memo` et `useMemo`

Recommended resources

Bonjour ! Bienvenue dans cette nouvelle leçon du module sur la performance.

Dans la leçon précédente, nous avons vu comment faire communiquer une interface utilisateur 2D avec une scène 3D, une étape clé pour créer des applications interactives. Maintenant que vous savez assembler des scènes riches et dynamiques, une nouvelle question se pose : comment s'assurer que tout reste fluide et performant ? Une scène 3D, même simple, peut rapidement consommer beaucoup de ressources si elle n'est pas optimisée.

L'objectif de cette leçon est d'aborder la première ligne de défense contre les problèmes de performance en React : prévenir les re-calculs et les re-rendus inutiles. Vous apprendrez à utiliser deux outils fondamentaux de l'écosystème React, React.memo et useMemo, qui sont absolument essentiels pour optimiser des applications React Three Fiber.

1. Comprendre le "Rendu" et son Coût

Avant d'optimiser, il faut comprendre ce que l'on optimise. En React, le terme "rendu" (ou render) signifie que React exécute le code de votre fonction de composant pour savoir à quoi doit ressembler l'interface. Un re-rendu n'implique pas forcément une modification visible à l'écran (le DOM), mais c'est une étape de calcul qui peut être coûteuse.

Deux situations principales déclenchent le re-rendu d'un composant :

  1. Un changement de son propre état (via useState, par exemple).
  2. Le re-rendu de son composant parent.

Ce deuxième point est souvent la source de re-rendus inutiles. Si un composant parent se met à jour, tous ses enfants se re-rendent par défaut, même si leurs propres props n'ont pas changé.

Pour bien visualiser ce processus, la vidéo suivante est un excellent point de départ.

Apprendre React : Mémoisation et useCallback

Cette vidéo de Grafikart.fr explique clairement ce qu'est un rendu, ce qui le déclenche, et pourquoi un composant enfant se re-rend même s'il ne semble pas avoir changé.

Regardez le début de la vidéo, de 00:00:00 à 00:04:08. Concentrez-vous sur les deux raisons qui provoquent un re-rendu d'un composant.

Dans une application R3F, un re-rendu inutile peut signifier recalculer des géométries, réévaluer des logiques complexes dans useFrame, ou déclencher des opérations coûteuses sur le GPU. Il est donc crucial de les maîtriser.


2. Comment Repérer les Re-rendus Inutiles ?

La première étape de toute optimisation est de mesurer. Il ne faut jamais optimiser "à l'aveugle". L'outil indispensable pour cela est le Profiler des Outils de Développement React (React DevTools).

Le Profiler vous permet d'enregistrer les interactions dans votre application et de visualiser quels composants se sont re-rendus, pourquoi, et combien de temps cela a pris.

Apprendre React : Mémoisation et useCallback

La suite de la vidéo précédente montre comment utiliser le Profiler pour identifier les composants qui se re-rendent et qui posent potentiellement problème.

Regardez de 00:04:08 à 00:08:11. Observez comment le graphe en barres (flamegraph) colore les composants qui ont été rendus et comment on peut inspecter la cause du rendu ("rendered because parent rendered").

Une fois que vous avez identifié un composant coûteux qui se re-rend inutilement, vous pouvez utiliser des techniques de mémoïsation.


3. Mémoïser des Calculs Coûteux avec useMemo

La mémoïsation est une technique d'optimisation qui consiste à mettre en cache le résultat d'une fonction et à le retourner si les mêmes entrées se présentent à nouveau, au lieu de recalculer le résultat.

Le hook useMemo applique ce principe. Il est parfait pour des calculs lourds (trier un grand tableau, effectuer une opération mathématique complexe, etc.) qui n'ont pas besoin d'être refaits à chaque rendu si leurs dépendances n'ont pas changé.

const memoizedValue = useMemo(() => {
  // Fonction de calcul coûteuse
  return computeExpensiveValue(a, b);
}, [a, b]); // Le tableau de dépendances

Le calcul ne sera ré-exécuté que si a ou b change.

Performance Comparison: Direct Function Call vs. useMemo
Cette image illustre la différence de performance : l'approche "Pro" utilisant `useMemo` est significativement plus rapide car elle évite le recalcul inutile de la valeur à chaque rendu.

La vidéo suivante illustre parfaitement ce concept avec un exemple simple et clair.

Apprendre React : Le hook useMemo

Cette vidéo de Grafikart.fr vous montrera un cas concret où useMemo résout un problème de performance évident.

Regardez les sections de 00:00:00 à 00:03:13. Observez le ralentissement causé par une fonction lente lorsque l'on interagit avec une partie non liée de l'interface, et comment useMemo résout ce problème en ne ré-exécutant le calcul que lorsque c'est nécessaire.

Pour une référence écrite, la documentation officielle est une excellente ressource.

useMemo - React

La documentation de React explique en détail le fonctionnement et les cas d'usage de useMemo.

Lisez la section "Sauter des recalculs coûteux". Elle renforce ce que vous venez de voir en vidéo et explique comment déterminer si un calcul est suffisamment coûteux pour justifier la mémoïsation.


4. Mémoïser des Composants avec React.memo

Maintenant que nous savons mémoïser une valeur, comment mémoïser un composant entier ? C'est le rôle de React.memo.

React.memo est un "Composant d'Ordre Supérieur" (Higher-Order Component ou HOC) qui enrobe votre composant. Si le composant enrobé est sur le point de se re-rendre avec les mêmes props que lors du rendu précédent, React sautera le re-rendu et réutilisera le résultat précédent.

function MyComponent(props) {
  /* le code du rendu */
}

// Enrobage avec memo
const MemoizedComponent = React.memo(MyComponent);
Demonstration of React.memo for Preventing Unnecessary Component Re-renders
Ce GIF montre un composant `Button` enrobé dans `React.memo`. Même si le compteur du parent change (déclenchant un re-rendu du parent), le message "rendering button" n'apparaît pas dans la console, prouvant que le bouton lui-même n'a pas été re-rendu.

Reprenons notre première vidéo pour voir React.memo en action.

Apprendre React : Mémoisation et useCallback

La vidéo que nous avons commencée explique comment implémenter React.memo pour empêcher un composant de se re-rendre inutilement.

Regardez la section de 00:09:13 à 00:11:31. Notez comment l'enrobage du composant Info avec memo l'empêche de s'afficher dans les logs de rendu lorsque l'état du parent change.


5. La Synergie Indispensable : memo, useMemo et useCallback

Vous pourriez penser qu'il suffit d'enrober tous vos composants dans React.memo. Malheureusement, c'est un peu plus complexe. React.memo effectue une comparaison superficielle (shallow comparison) des props. Si une des props est un objet, un tableau ou une fonction, cette comparaison échouera la plupart du temps.

Pourquoi ? Parce qu'en JavaScript, les objets, tableaux et fonctions sont des types par référence. Une fonction ou un objet défini à l'intérieur d'un composant est recréé à chaque rendu, obtenant une nouvelle référence en mémoire.

function Parent() {
  const [count, setCount] = useState(0);

  // `config` est un NOUVEL objet à chaque rendu de Parent
  const config = { color: 'blue' }; 
  
  // `handleClick` est une NOUVELLE fonction à chaque rendu de Parent
  const handleClick = () => console.log('clicked');

  return (
    <div>
      <button onClick={() => setCount(c => c + 1)}>Increment</button>
      {/* Même si `config` et `handleClick` ont l'air identiques,
          leurs références changent, donc MemoizedChild se re-rendra toujours ! */}
      <MemoizedChild config={config} onClick={handleClick} />
    </div>
  );
}

C'est là que useMemo et son cousin, useCallback, deviennent essentiels pour faire fonctionner React.memo :

  • useMemo est utilisé pour mémoïser des objets ou des tableaux.
  • useCallback est utilisé pour mémoïser des fonctions.

useCallback est simplement un sucre syntaxique pour useMemo. Les deux lignes suivantes sont équivalentes :
useCallback(fn, deps) est équivalent à useMemo(() => fn, deps).

Voyons comment stabiliser nos props pour que React.memo fonctionne enfin.

Apprendre React : Mémoisation et useCallback

La dernière partie de la vidéo de Grafikart explique ce problème des props instables (non-stables) et comment useCallback (présenté ici via useMemo) le résout.

Regardez de 00:11:31 à 00:15:48. C'est la partie la plus importante. Comprenez pourquoi passer une fonction directement en prop annule l'effet de memo, et comment useCallback (ou useMemo pour une fonction) résout ce problème en garantissant que la référence de la fonction reste la même entre les rendus.

La documentation de React explique également très bien cette synergie.

useMemo - React

Cette section de la documentation est cruciale. Elle montre comment useMemo est utilisé pour stabiliser un tableau passé en prop à un composant enfant mémoïsé.

Lisez la section "Éviter les rendus superflus de composants". Elle démontre clairement pourquoi un composant List enrobé avec memo continuerait de se re-rendre sans useMemo sur la prop visibleTodos.


6. Règle d'Or : Ne Pas Optimiser Prématurément

La mémoïsation a un coût : elle consomme de la mémoire et ajoute de la complexité. L'utiliser partout serait contre-productif.

La règle d'or est : mesurer d'abord, optimiser ensuite.

  1. Construisez votre application de la manière la plus simple possible.
  2. Utilisez le Profiler pour identifier les goulots d'étranglement et les re-rendus lents.
  3. Appliquez useMemo, useCallback et React.memo de manière ciblée, uniquement là où vous avez constaté un réel gain de performance.

Ces articles résument bien cette philosophie.

React Hooks #3 : La performance avec useCallback et useMemo

Cet article fournit d'excellents conseils sur l'utilisation judicieuse des hooks de performance.

Lisez les sections "À noter" et "Conclusion". Elles insistent sur le fait que ces hooks sont des outils d'optimisation, pas de construction, et doivent être utilisés avec parcimonie.


Conclusion

Dans cette leçon, nous avons exploré les fondations de l'optimisation de performance dans React, une compétence indispensable pour le développement 3D avec R3F.

Points clés à retenir :

  • Les composants React se re-rendent lorsque leur état change ou lorsque leur parent se re-rend.
  • Le Profiler des React DevTools est l'outil principal pour identifier les re-rendus inutiles et coûteux.
  • useMemo met en cache le résultat d'un calcul pour éviter de le refaire inutilement.
  • React.memo met en cache le rendu d'un composant pour l'éviter si ses props n'ont pas changé.
  • Pour que React.memo soit efficace, les props de type objet, tableau ou fonction doivent être stabilisées avec useMemo ou useCallback.
  • N'optimisez pas prématurément : mesurez toujours avant d'appliquer des techniques de mémoïsation.

Aperçu de la prochaine leçon :

Maintenant que vous savez optimiser la logique de vos composants, nous allons nous attaquer à un problème spécifique à la 3D : comment afficher des milliers d'objets identiques de manière performante ? La réponse réside dans une technique puissante appelée instancing. Nous verrons comment l'implémenter dans Three.js et R3F avec InstancedMesh.