Menu

useMemo en React : mettre en cache les calculs coûteux

useMemo met en cache le résultat d'un calcul entre les rendus et ne le recalcule que lorsque ses dépendances changent. Découvrez la syntaxe, comment garder un objet stable pour un enfant memo, et quand useMemo n'en vaut pas la peine.

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

useMemo met en cache le résultat d'un calcul entre les rendus. React exécute votre fonction au premier rendu, garde le résultat, et renvoie ce même résultat aux rendus suivants jusqu'à ce qu'une des valeurs du tableau de dépendances change.

Ouvrez la console et basculez le thème plusieurs fois : rien ne s'affiche, et la bascule semble instantanée. Changez d'onglet et filtering for active apparaît, avec une courte pause, car tab est une dépendance. Remplacez maintenant la ligne useMemo par const visible = filterTodos(todos, tab); et basculez à nouveau le thème : chaque clic écrit dans la console et bloque pendant 100 ms.

La syntaxe

const cachedValue = useMemo(calculateValue, dependencies);

const visible = useMemo(() => filterTodos(todos, tab), [todos, tab]);
  • calculateValue est une fonction sans argument qui renvoie la valeur que vous voulez mettre en cache. React l'appelle pendant le rendu.
  • dependencies est la liste de chaque valeur du composant que la fonction lit : props, état et variables déclarées dans le corps du composant.
  • À chaque rendu, React compare chaque dépendance à sa valeur du rendu précédent avec Object.is. Si toutes correspondent, il renvoie la valeur en cache sans appeler votre fonction. Si l'une diffère, il appelle la fonction et met le nouveau résultat en cache.

useMemo est un hook, il suit donc les règles des hooks : appelez-le au niveau supérieur du composant, pas dans une boucle ni dans une condition.

Les dépendances doivent être complètes

Omettez une valeur du tableau et le cache continue de servir un résultat calculé à partir de son ancienne valeur. Ici, le total lit qty mais ne liste que price :

Cliquez sur Quantity : elle passe à 2, mais le total reste à 10 et rien ne s'affiche. Cliquez sur Price et le total saute à 22, car c'est seulement alors que le calcul s'exécute à nouveau, en lisant cette fois le qty actuel. Ajoutez qty au tableau et les deux boutons mettent à jour le total. La règle de lint react-hooks/exhaustive-deps détecte cette erreur dans un vrai projet.

L'erreur inverse est une dépendance nouvelle à chaque rendu, comme un objet créé dans le corps du composant. Elle ne correspond jamais à sa valeur précédente, donc le cache n'est jamais utilisé :

const filters = { tab, query }; // a new object every render
const visible = useMemo(() => applyFilters(todos, filters), [todos, filters]); // recomputes every time

// List the primitive values instead
const visible = useMemo(() => applyFilters(todos, { tab, query }), [todos, tab, query]);

Garder un objet stable pour un enfant memo

La deuxième raison d'utiliser useMemo n'a rien à voir avec les calculs lents. Un enfant enveloppé dans memo ne saute son rendu que si chaque prop est la même que la fois précédente. Un littéral d'objet écrit dans le corps du composant est un nouvel objet à chaque rendu, il met donc memo en échec même quand son contenu n'a pas changé. useMemo garde le même objet jusqu'à ce que ses entrées changent.

Tapez dans le champ de note et Chart reste silencieux, tandis que Switch unit affiche un rendu. Remplacez l'appel à useMemo par un simple const options = { unit, max: unit === 'km' ? 100 : 62 }; et chaque frappe dans la note affiche render Chart.

La même chose vaut pour un effet qui dépend d'un objet. Si un effet liste options dans ses dépendances et que options est reconstruit à chaque rendu, l'effet s'exécute après chaque rendu. useMemo corrige cela, mais la solution la plus simple est souvent de créer l'objet dans l'effet et de dépendre des valeurs primitives :

useEffect(() => {
    const options = { serverUrl, roomId };
    const connection = createConnection(options);
    connection.connect();
    return () => connection.disconnect();
}, [serverUrl, roomId]);

Quand ne pas utiliser useMemo

La plupart des calculs dans un composant sont peu coûteux : additionner des nombres, concaténer deux chaînes, filtrer quelques dizaines d'éléments. Les mettre en cache ne fait presque rien gagner : React stocke quand même la valeur et compare les dépendances à chaque rendu, et le code devient plus difficile à lire.

// No useMemo needed
const fullName = first + ' ' + last;
const doneCount = todos.filter((t) => t.done).length;

Pour savoir si quelque chose est lent, mesurez-le. Entourez le calcul de console.time('filter') et console.timeEnd('filter'), effectuez l'interaction et regardez le nombre. En ordre de grandeur, un travail qui prend une milliseconde ou plus sur votre machine est un candidat, sachant que les appareils de vos utilisateurs peuvent être plus lents. Avant de recourir à useMemo, vérifiez aussi si ce travail a sa place dans le rendu : des données qui ne changent jamais peuvent être calculées une fois hors du composant, et un état qu'une seule petite partie de la page utilise peut descendre dans un composant plus petit (voir le rendu).

useMemo ou useEffect pour les valeurs dérivées

Un pattern courant consiste à calculer une valeur dans un effet et à la stocker dans l'état :

// Two renders per change, and the first one has stale data
const [visible, setVisible] = useState([]);
useEffect(() => {
    setVisible(filterTodos(todos, tab));
}, [todos, tab]);

// One render, always in sync
const visible = useMemo(() => filterTodos(todos, tab), [todos, tab]);

La version avec effet fait un rendu avec l'ancien visible, puis exécute l'effet, modifie l'état et refait un rendu. C'est un rendu de plus, et dans le premier la liste ne correspond pas à l'onglet. Calculer pendant le rendu avec useMemo (ou sans aucun hook, si le travail est peu coûteux) donne la bonne valeur dans le même rendu. Les effets servent à se synchroniser avec des choses extérieures à React, pas à dériver un élément d'état d'un autre.

Une indication de performance, pas une garantie

useMemo indique à React qu'il peut réutiliser un résultat. Il ne promet pas qu'il le fera. React garde la valeur en cache en fonctionnement normal, mais il peut la jeter, par exemple quand vous modifiez le fichier pendant le développement, ou quand un composant se suspend lors de son premier montage. Écrivez le code pour qu'il reste correct si le calcul s'exécute à nouveau : la fonction doit être pure, sans effets de bord comme des requêtes, des abonnements ou la modification de variables extérieures. Les effets de bord ont leur place dans un gestionnaire d'événement ou un effet.

En développement, sous StrictMode, React appelle votre fonction de calcul deux fois pour vous aider à repérer du code impur. L'aperçu fonctionne ici comme un build de production, vous verrez donc chaque message filtering for une seule fois.

useMemo et React Compiler

React Compiler 1.0 est un outil de build qui analyse vos composants et ajoute la mémoïsation automatiquement, en mettant en cache les calculs et le JSX là où useMemo, useCallback et memo seraient utiles. Dans un projet qui l'utilise, vous en écrirez moins à la main. useMemo écrit à la main fonctionne toujours à côté du compilateur, reste l'outil à utiliser quand le compilateur n'est pas configuré, et vous le rencontrerez dans la plupart du code React existant.

Questions fréquentes

Que fait useMemo dans React ?

Il appelle votre fonction de calcul au premier rendu, stocke le résultat et, aux rendus suivants, renvoie le résultat stocké tant que chaque valeur du tableau de dépendances est la même que la fois précédente (comparée avec Object.is).

Quand utiliser useMemo ?

Quand un calcul est sensiblement lent et que ses entrées changent rarement, ou quand un objet ou un tableau doit garder la même référence d'un rendu à l'autre parce qu'un enfant memo ou un effet en dépend. Pour un travail peu coûteux, passez-vous-en.

Quelle est la différence entre useMemo et useCallback ?

useMemo met en cache la valeur que renvoie votre fonction. useCallback met en cache la fonction elle-même. useCallback(fn, deps) équivaut à useMemo(() => fn, deps).

useMemo garantit-il que la valeur ne sera jamais recalculée ?

Non. C'est une indication de performance. React peut jeter la valeur en cache dans certains cas, par exemple en développement quand vous modifiez le fichier, donc votre code doit rester correct si le calcul s'exécute à nouveau.

Ai-je encore besoin de useMemo avec React Compiler ?

React Compiler peut ajouter la mémoïsation automatiquement quand votre build l'utilise. Sans le compilateur, ou dans les cas qu'il ignore, useMemo écrit à la main fonctionne toujours de la même façon et reste courant dans le code existant.

Illustration des langages de programmation de Coddy

Apprendre à coder avec Coddy

COMMENCER