
`useMemo` : Mémoïsation des valeurs calculées
Apprenez à utiliser le hook useMemo en React pour mémoïser les résultats de calculs coûteux et éviter des recalculs inutiles lors des re-rendus.
Le problème : Recalculs coûteux à chaque rendu
Dans un composant React, il est fréquent d'effectuer des calculs ou des transformations de données basées sur les props ou l'état pour obtenir une valeur qui sera utilisée dans le rendu. Par exemple, filtrer une grande liste, trier des données, effectuer une opération mathématique complexe, etc.
Par défaut, le code à l'intérieur d'un composant fonctionnel s'exécute à chaque fois que le composant se re-rend. Cela signifie que même si les données d'entrée du calcul (props ou état) n'ont pas changé, le calcul sera quand même refait si le composant se re-rend pour une autre raison (par exemple, un changement d'état non lié, ou un re-rendu du parent).
Si ce calcul est coûteux en termes de temps CPU, le refaire inutilement peut dégrader les performances et rendre l'interface moins réactive. C'est là qu'intervient le hook `useMemo`. Il permet de "mémoïser" (mettre en cache) le résultat d'un calcul coûteux et de ne le recalculer que lorsque ses dépendances ont réellement changé.
Qu'est-ce que `useMemo` et comment ça marche ?
Le hook `useMemo` est conçu pour mémoriser la valeur de retour d'une fonction. Il prend deux arguments :
- Une fonction "create" qui effectue le calcul coûteux et retourne la valeur à mémoïser.
- Un tableau de dépendances, similaire à celui de `useEffect` ou `useCallback`.
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);Le fonctionnement est le suivant :
- Lors du premier rendu, React exécute la fonction "create" (`() => computeExpensiveValue(a, b)`), stocke (mémoïse) sa valeur de retour, et la retourne dans `memoizedValue`.
- Lors des rendus suivants, React compare les valeurs actuelles des dépendances (`[a, b]`) avec celles du rendu précédent.
- Si aucune des dépendances n'a changé (comparaison `Object.is`), React ne ré-exécute pas la fonction "create" et retourne simplement la valeur précédemment mémoïsée.
- Si au moins une des dépendances a changé, React ré-exécute la fonction "create", mémoïse la nouvelle valeur retournée, et la retourne.
En bref, `useMemo` garantit que le calcul coûteux n'est refait que lorsque c'est strictement nécessaire, c'est-à-dire quand les données dont il dépend ont changé.
Cas d'usage 1 : Optimiser les calculs coûteux
Le cas d'usage le plus direct est d'envelopper un calcul qui prend du temps ou consomme beaucoup de ressources.
import React, { useState, useMemo } from 'react';
// Simulation d'une fonction coûteuse
function fibonacci(n) {
if (n <= 1) return n;
let a = 0, b = 1, temp;
for (let i = 2; i <= n; i++) {
temp = a + b;
a = b;
b = temp;
}
// Ajout d'un délai artificiel pour visualiser l'impact
let i = 0; while (i < 100000000) { i++; }
return b;
}
function CalculateurFibonacci() {
const [nombre, setNombre] = useState(30);
const [theme, setTheme] = useState('light'); // Etat non lié au calcul
// Sans useMemo, fibonacci(nombre) serait recalculé à chaque changement de theme
// const resultatFib = fibonacci(nombre);
// Avec useMemo, fibonacci(nombre) n'est recalculé que si 'nombre' change
const resultatFib = useMemo(() => {
console.log(`Calcul de Fibonacci pour ${nombre}...`);
return fibonacci(nombre);
}, [nombre]); // Dépendance: nombre
const styles = {
backgroundColor: theme === 'light' ? '#FFF' : '#333',
color: theme === 'light' ? '#000' : '#FFF',
padding: '20px'
};
return (
Calculateur Fibonacci
Fibonacci({nombre}) = {resultatFib}
Changer le thème ne devrait pas relancer le calcul de Fibonacci.
);
}
export default CalculateurFibonacci;Dans cet exemple, même si l'on change le thème (ce qui re-rend le composant), le calcul coûteux de `fibonacci` n'est pas refait car la dépendance `nombre` n'a pas changé. `useMemo` retourne la valeur précédemment calculée, rendant le changement de thème instantané.
Cas d'usage 2 : Maintenir l'égalité référentielle pour les props
Un autre cas d'usage très important de `useMemo` est de garantir l'égalité référentielle pour les objets ou les tableaux passés comme props à des composants enfants mémoïsés avec `React.memo`.
Comme nous l'avons vu avec `React.memo`, si un composant parent crée un nouvel objet ou tableau littéral à chaque rendu et le passe comme prop, `React.memo` ne pourra pas optimiser l'enfant car la référence de la prop change à chaque fois.
`useMemo` permet de résoudre ce problème en mémoïsant l'objet ou le tableau lui-même. Tant que les dépendances utilisées pour créer cet objet/tableau ne changent pas, `useMemo` retournera la même instance (la même référence).
import React, { useState, useMemo } from 'react';
const EnfantMemo = React.memo(function EnfantMemo({ config }) {
console.log("Rendu EnfantMemo");
return Thème: {config.theme}, Police: {config.fontSize};
});
function ParentAvecObjetProp() {
const [compteur, setCompteur] = useState(0);
const [theme, setTheme] = useState('light');
// Sans useMemo, 'configStyle' est une nouvelle référence à chaque rendu
// const configStyle = { theme: theme, fontSize: 14 };
// Avec useMemo, 'configStyle' garde la même référence tant que 'theme' ne change pas
const configStyle = useMemo(() => ({
theme: theme,
fontSize: 14
}), [theme]); // Dépendance: theme
return (
);
}
export default ParentAvecObjetProp;Ici, grâce à `useMemo`, l'objet `configStyle` ne change de référence que lorsque `theme` change. Si seul `compteur` change, `EnfantMemo` recevra la même référence pour la prop `config`, et `React.memo` pourra sauter son re-rendu.
Règles des dépendances et optimisation prématurée
Comme pour `useEffect` et `useCallback`, il est crucial de spécifier correctement le tableau de dépendances de `useMemo`. Omettre une dépendance utilisée dans la fonction "create" peut conduire à l'utilisation de valeurs obsolètes (stale closures). Inclure des dépendances inutiles peut annuler la mémoïsation. Fiez-vous à l'outil de linting ESLint (`eslint-plugin-react-hooks`) pour vous aider à gérer les dépendances.
Attention à l'optimisation prématurée : `useMemo` n'est pas gratuit. Il consomme de la mémoire pour stocker la valeur mémoïsée et ajoute une étape de comparaison des dépendances à chaque rendu. N'utilisez `useMemo` que lorsque :
- Le calcul est réellement coûteux (mesurez avec le Profiler si nécessaire).
- Vous avez besoin de préserver l'égalité référentielle pour une prop (objet ou tableau) passée à un enfant mémoïsé avec `React.memo`.
Pour des calculs simples et rapides, le coût de `useMemo` peut dépasser le bénéfice de la mémoïsation. Ne l'appliquez pas partout aveuglément.
En résumé, `useMemo` est un hook d'optimisation puissant pour éviter des recalculs coûteux et pour stabiliser les références des objets et tableaux passés en props, contribuant ainsi à améliorer les performances de rendu de vos applications React.