cache🌐 cache – React - Cette fonctionnalitĂ© est disponible dans le dernier Canary - Cette fonctionnalitĂ© est disponible dans le dernier Canary - Cette fonctionnalitĂ© est disponible dans le dernier Canary - Cette fonctionnalitĂ© est disponible dans le dernier Canary - Cette fonctionnalitĂ© est disponible dans le dernier Canary - Cette fonctionnalitĂ© est disponible dans le dernier Canary - Cette fonctionnalitĂ© est disponible dans le dernier Canary - Cette fonctionnalitĂ© est disponible dans le dernier Canary - Cette fonctionnalitĂ© est disponible dans le dernier Canary - Cette fonctionnalitĂ© est disponible dans le dernier Canary - Cette fonctionnalitĂ© est disponible dans le dernier Canary - Cette fonctionnalitĂ© est disponible dans le dernier Canary - Cette fonctionnalitĂ© est disponible dans le dernier Canary - Cette fonctionnalitĂ© est disponible dans le dernier Canary - Cette fonctionnalitĂ© est disponible dans le dernier Canary - Cette fonctionnalitĂ© est disponible dans le dernier Canary - Cette fonctionnalitĂ© est disponible dans le dernier Canary - Cette fonctionnalitĂ© est disponible dans le dernier Canary - Cette fonctionnalitĂ© est disponible dans le dernier Canary - Cette fonctionnalitĂ© est disponible dans le dernier Canary - Cette fonctionnalitĂ© est disponible dans le dernier Canary - Cette fonctionnalitĂ© est disponible dans le dernier Canary - Cette fonctionnalitĂ© est disponible dans le dernier Canary - Cette fonctionnalitĂ© est disponible dans le dernier Canary - Cette fonctionnalitĂ© est disponible dans le dernier Canary - fr.react.dev

Canary (fonctionnalité expérimentale)

cache vous permet de mettre en cache le rĂ©sultat d’un chargement de donnĂ©es ou d’un calcul.

const cachedFn = cache(fn);

Référence

cache(fn)

Appelez cache hors de tout composant pour crĂ©er une variante d’une fonction dotĂ©e de mise en cache.

import {cache} from 'react';
import calculateMetrics from 'lib/metrics';

const getMetrics = cache(calculateMetrics);

function Chart({data}) {
const report = getMetrics(data);
// ...
}

Lors du premier appel de getMetrics avec data, getMetrics appellera calculateMetrics(data) et mettra le rĂ©sultat en cache. Si getMetrics est rappelĂ©e avec le mĂȘme argument data, elle renverra le rĂ©sultat mis en cache plutĂŽt que de rappeler calculateMetrics(data).

Voir d’autres exemples plus bas.

ParamĂštres

  • fn : la fonction dont vous voulez mettre les rĂ©sultats en cache. fn peut prendre un nombre quelconque d’arguments et renvoyer n’importe quel type de rĂ©sultat.

Valeur renvoyée

cache renvoie une version de fn dotĂ©e d’un cache, avec la mĂȘme signature de type. Elle n’appelle pas fn Ă  ce moment-lĂ .

Lors d’un appel Ă  cachedFn avec des arguments donnĂ©s, elle vĂ©rifiera d’abord si un rĂ©sultat correspondant existe dans le cache. Si tel est le cas, elle renverra ce rĂ©sultat. Dans le cas contraire, elle appellera fn avec les arguments, mettra le rĂ©sultat en cache et le renverra. fn n’est appelĂ©e qu’en cas d’absence de correspondance dans le cache (cache miss, NdT).

Remarque

L’optimisation qui consiste Ă  mettre en cache les valeurs rĂ©sultats sur base des arguments passĂ©s est gĂ©nĂ©ralement appelĂ©e mĂ©moĂŻsation. La fonction renvoyĂ©e par cache est dite « fonction mĂ©moĂŻsĂ©e Â».

Limitations

  • React invalidera le cache de toutes les fonctions mĂ©moĂŻsĂ©es Ă  chaque requĂȘte serveur.
  • Chaque appel Ă  cache crĂ©e une nouvelle fonction. Ça signifie qu’appeler cache plusieurs fois avec la mĂȘme fonction renverra plusieurs fonctions mĂ©moĂŻsĂ©es distinctes, avec chacune leur propre cache.
  • cachedFn mettra Ă©galement les erreurs en cache. Si fn lĂšve une exception pour certains arguments, ce sera mis en cache, et la mĂȘme erreur sera levĂ©e lorsque cachedFn sera rappelĂ©e avec ces mĂȘmes arguments.
  • cache est destinĂ©e uniquement aux Composants Serveur.

Utilisation

Mettre en cache un calcul coûteux

Utilisez cache pour éviter de dupliquer un traitement.

import {cache} from 'react';
import calculateUserMetrics from 'lib/user';

const getUserMetrics = cache(calculateUserMetrics);

function Profile({user}) {
const metrics = getUserMetrics(user);
// ...
}

function TeamReport({users}) {
for (const user of users) {
const metrics = getUserMetrics(user);
// ...
}
// ...
}

Si le mĂȘme objet user est affichĂ© dans Profile et TeamReport, les deux composants peuvent mutualiser le travail et n’appeler calculateUserMetrics qu’une fois pour ce user.

Supposons que Profile fasse son rendu en premier. Il appellera getUserMetrics, qui vĂ©rifiera si un rĂ©sultat existe en cache. Comme il s’agit du premier appel de getUserMetrics pour ce user, elle ne trouvera aucune correspondance. getUserMetrics appellera alors effectivement calculateUserMetrics avec ce user puis mettra le rĂ©sultat en cache.

Lorsque TeamReport affichera sa liste de users et atteindra le mĂȘme objet user, il appellera getUserMetrics qui lira le rĂ©sultat depuis son cache.

PiĂšge

Appeler des fonctions mémoïsées distinctes lira des caches distincts

Pour partager un cache, des composants doivent appeler la mĂȘme fonction mĂ©moĂŻsĂ©e.

// Temperature.js
import {cache} from 'react';
import {calculateWeekReport} from './report';

export function Temperature({cityData}) {
// đŸš© ErronĂ© : Appeler `cache` au sein du composant
// crée une nouvelle `getWeekReport` à chaque rendu
const getWeekReport = cache(calculateWeekReport);
const report = getWeekReport(cityData);
// ...
}
// Precipitation.js
import {cache} from 'react';
import {calculateWeekReport} from './report';

// đŸš© ErronĂ© : `getWeekReport` n’est accessible que depuis
// le composant `Precipitation`.
const getWeekReport = cache(calculateWeekReport);

export function Precipitation({cityData}) {
const report = getWeekReport(cityData);
// ...
}

Dans l’exemple ci-dessus, Precipitation et Temperature appellent chacun cache pour crĂ©er une nouvelle fonction mĂ©moĂŻsĂ©e, qui dispose Ă  chaque fois de son propre cache. Si les deux composants s’affichent avec les mĂȘmes cityData, ils dupliqueront tout de mĂȘme le travail en appelant Ă  chaque fois calculateWeekReport.

Qui plus est, Temperature crée une nouvelle fonction mémoïsée à chaque rendu, ce qui ne permet aucun partage de cache.

Pour maximiser les correspondances trouvĂ©es et rĂ©duire la charge de calcul, les deux composants devraient s’assurer de partager la mĂȘme fonction mĂ©moĂŻsĂ©e, pour pouvoir accĂ©der au mĂȘme cache. DĂ©finissez plutĂŽt la fonction mĂ©moĂŻsĂ©e dans un module dĂ©diĂ© qui peut faire l’objet d’un import dans les divers composants.

// getWeekReport.js
import {cache} from 'react';
import {calculateWeekReport} from './report';

export default cache(calculateWeekReport);
// Temperature.js
import getWeekReport from './getWeekReport';

export default function Temperature({cityData}) {
const report = getWeekReport(cityData);
// ...
}
// Precipitation.js
import getWeekReport from './getWeekReport';

export default function Precipitation({cityData}) {
const report = getWeekReport(cityData);
// ...
}

DĂ©sormais les deux composants appellent la mĂȘme fonction mĂ©moĂŻsĂ©e, exportĂ©e depuis ./getWeekReport.js, afin de lire et d’écrire dans le mĂȘme cache.

Partager un instantané de données

Pour partager un instantanĂ© de donnĂ©es d’un composant Ă  l’autre, appelez cache sur une fonction de chargement de donnĂ©es telle que fetch. Lorsque plusieurs composants feront le mĂȘme chargement de donnĂ©es, seule une requĂȘte sera faite, et ses donnĂ©es rĂ©sultantes mises en cache et partagĂ©es Ă  travers plusieurs composants. Tous les composants utiliseront le mĂȘme instantanĂ© de ces donnĂ©es au sein du rendu cĂŽtĂ© serveur.

import {cache} from 'react';
import {fetchTemperature} from './api.js';

const getTemperature = cache(async (city) => {
return await fetchTemperature(city);
});

async function AnimatedWeatherCard({city}) {
const temperature = await getTemperature(city);
// ...
}

async function MinimalWeatherCard({city}) {
const temperature = await getTemperature(city);
// ...
}

Si AnimatedWeatherCard et MinimalWeatherCard s’affichent tous deux avec la mĂȘme city, ils recevront le mĂȘme instantanĂ© de donnĂ©es depuis la fonction mĂ©moĂŻsĂ©e.

Si AnimatedWeatherCard et MinimalWeatherCard fournissent un argument city diffĂ©rent Ă  getTemperature, alors fetchTemperature sera appelĂ©e deux fois, et chaque point d’appel recevra ses donnĂ©es spĂ©cifiques.

La city agit comme une clé de cache.

Remarque

Le rendu asynchrone n’est possible que dans les Composants Serveur.

async function AnimatedWeatherCard({city}) {
const temperature = await getTemperature(city);
// ...
}

Précharger des données

En mettant en cache un chargement de donnĂ©es qui prendrait du temps, vous pouvez dĂ©marrer des traitements asynchrones avant de faire le rendu d’un composant.

const getUser = cache(async (id) => {
return await db.user.query(id);
});

async function Profile({id}) {
const user = await getUser(id);
return (
<section>
<img src={user.profilePic} />
<h2>{user.name}</h2>
</section>
);
}

function Page({id}) {
// ✅ Malin : commence Ă  charger les donnĂ©es utilisateur
getUser(id);
// ... des calculs ici
return (
<>
<Profile id={id} />
</>
);
}

Lorsque Page fait son rendu, le composant appelle getUser, mais remarquez qu’il n’utilise pas les donnĂ©es renvoyĂ©es. Cet appel anticipĂ© Ă  getUser dĂ©clenche la requĂȘte asynchrone Ă  la base de donnĂ©es, qui s’exĂ©cute pendant que Page fait d’autres calculs puis dĂ©clenche le rendu de ses enfants.

Lorsque Profile fait son rendu, nous appelons Ă  nouveau getUser. Si l’appel initial Ă  getUser a fini son chargement et mis en cache les donnĂ©es utilisateur, lorsque Profile demande ces donnĂ©es puis attend, il n’a plus qu’à les lire du cache, sans relancer un appel rĂ©seau. Si la requĂȘte de donnĂ©es initiale n’est pas encore terminĂ©e, cette approche de prĂ©chargement rĂ©duit tout de mĂȘme le dĂ©lai d’obtention des donnĂ©es.

En détail

Mettre en cache un traitement asynchrone

Lorsque vous Ă©valuez une fonction asynchrone, vous recevez une Promise reprĂ©sentant le traitement. La promesse maintient un Ă©tat pour le traitement (en attente, accompli ou rejetĂ©) ainsi que l’aboutissement du traitement Ă  terme.

Dans cet exemple, la fonction asynchrone fetchData renvoie une promesse pour le résultat de notre appel à fetch.

async function fetchData() {
return await fetch(`https://...`);
}

const getData = cache(fetchData);

async function MyComponent() {
getData();
// ... des calculs ici
await getData();
// ...
}

En appelant getData pour la premiĂšre fois, la promesse renvoyĂ©e par fetchData est mise en cache. Les appels ultĂ©rieurs utiliseront la mĂȘme promesse.

Remarquez que le premier appel Ă  getData n’appelle pas await, alors que le second le fait. await est un opĂ©rateur JavaScript qui attend l’établissement de la promesse et renvoie son rĂ©sultat accompli (ou lĂšve son erreur de rejet). Le premier appel Ă  getData lance simplement le chargement (fetch) pour mettre la promesse en cache, afin que le deuxiĂšme getData la trouve dĂ©jĂ  en cours d’exĂ©cution.

Si lors du deuxiĂšme appel la promesse est toujours en attente, alors await attendra son rĂ©sultat. L’optimisation tient Ă  ce que, pendant le fetch issu du premier appel, React peut continuer son travail de calcul, ce qui rĂ©duit l’attente pour le deuxiĂšme appel.

Si la promesse est dĂ©jĂ  Ă©tablie Ă  ce moment-lĂ , await renverra immĂ©diatement la valeur accomplie (ou lĂšvera immĂ©diatement l’erreur de rejet). Dans les deux cas, on amĂ©liore la performance perçue.

PiĂšge

Appeler une fonction mĂ©moĂŻsĂ©e hors d’un composant n’utilisera pas le cache

import {cache} from 'react';

const getUser = cache(async (userId) => {
return await db.user.query(userId);
});

// đŸš© ErronĂ© : Appeler une fontion mĂ©moĂŻsĂ©e hors d’un composant
// n’exploitera pas le cache.
getUser('demo-id');

async function DemoProfile() {
// ✅ Correct : `getUser` exploitera le cache.
const user = await getUser('demo-id');
return <Profile user={user} />;
}

React ne fournit un accĂšs au cache pour les fonctions mĂ©moĂŻsĂ©es qu’au sein d’un composant. Si vous appelez getUser hors d’un composant, il Ă©valuera la fonction mais n’utilisera pas le cache (ni en lecture ni en Ă©criture).

C’est parce que l’accùs au cache est fourni via un contexte, et que les contextes ne sont accessibles que depuis les composants.

En détail

Comment choisir entre cache, memo et useMemo ?

Toutes ces API proposent de la mĂ©moĂŻsation, mais diffĂšrent sur ce que vous cherchez Ă  mĂ©moĂŻser, sur les destinataires du cache, et sur les mĂ©thodes d’invalidation de ce cache.

useMemo

Vous devriez gĂ©nĂ©ralement utiliser useMemo pour mettre en cache d’un rendu Ă  l’autre un calcul coĂ»teux dans un Composant Client. Ça pourrait par exemple mĂ©moĂŻser une transformation de donnĂ©es dans un composant.

'use client';

function WeatherReport({record}) {
const avgTemp = useMemo(() => calculateAvg(record), record);
// ...
}

function App() {
const record = getRecord();
return (
<>
<WeatherReport record={record} />
<WeatherReport record={record} />
</>
);
}

Dans cet exemple, App affiche deux WeatherReport avec le mĂȘme enregistrement. MĂȘme si les deux composants font le mĂȘme travail, ils ne peuvent pas partager des traitements. Le cache de useMemo est local Ă  chaque composant.

En revanche, useMemo s’assure bien que si App refait un rendu et que l’objet record n’a pas changĂ©, chaque instance du composant Ă©vitera son calcul et utilisera plutĂŽt sa valeur avgTemp mĂ©moĂŻsĂ©e. useMemo mettra le dernier calcul d’avgTemp en cache sur base des dĂ©pendances qu’on lui fournit.

cache

Vous utiliserez cache dans des Composants Serveur pour mémoïser du travail à partager entre plusieurs composants.

const cachedFetchReport = cache(fetchReport);

function WeatherReport({city}) {
const report = cachedFetchReport(city);
// ...
}

function App() {
const city = "Paris";
return (
<>
<WeatherReport city={city} />
<WeatherReport city={city} />
</>
);
}

En réécrivant l’exemple prĂ©cĂ©dent pour utiliser cache, cette fois la deuxiĂšme instance de WeatherReport pourra s’éviter une duplication d’effort et lira depuis le mĂȘme cache que le premier WeatherReport. Une autre diffĂ©rence avec l’exemple prĂ©cĂ©dent, c’est que cache est Ă©galement conseillĂ©e pour mĂ©moĂŻser des chargements de donnĂ©es, contrairement Ă  useMemo qui ne devrait ĂȘtre utilisĂ©e que pour des calculs.

Pour le moment, cache ne devrait ĂȘtre utilisĂ©e que dans des Composants Serveur, et le cache sera invalidĂ© Ă  chaque requĂȘte serveur.

memo

Vous devriez utiliser memo pour Ă©viter qu’un composant ne recalcule son rendu alors que ses props n’ont pas changĂ©.

'use client';

function WeatherReport({record}) {
const avgTemp = calculateAvg(record);
// ...
}

const MemoWeatherReport = memo(WeatherReport);

function App() {
const record = getRecord();
return (
<>
<MemoWeatherReport record={record} />
<MemoWeatherReport record={record} />
</>
);
}

Dans cet exemple, les deux composants MemoWeatherReport appelleront calculateAvg lors de leur premier rendu. Cependant, si App refait son rendu, sans pour autant changer record, aucune des props n’aura changĂ© et MemoWeatherReport ne refera pas son rendu.

Comparé à useMemo, memo mémoïse le rendu du composant sur base de ses props, au lieu de mémoïser des calculs spécifiques. Un peu comme avec useMemo, le composant mémoïsé ne met en cache que le dernier rendu, avec les derniÚres valeurs de props. DÚs que les props changent, le cache est invalidé et le composant refait son rendu.


Dépannage

Ma fonction mĂ©moĂŻsĂ©e est rĂ©-exĂ©cutĂ©e alors que je l’ai appelĂ©e avec les mĂȘmes arguments

Voyez dĂ©jĂ  les piĂšges signalĂ©s plus haut :

Si rien de tout ça ne s’applique, le problĂšme peut ĂȘtre liĂ© Ă  la façon dont React vĂ©rifie l’existence de quelque chose dans le cache.

Si vos arguments ne sont pas des primitives (ce sont par exemple des objets, des fonctions, des tableaux), assurez-vous de toujours passer la mĂȘme rĂ©fĂ©rence d’objet.

Lors d’un appel Ă  une fonction mĂ©moĂŻsĂ©e, React utilisera les arguments passĂ©s pour dĂ©terminer si un rĂ©sultat existe dĂ©jĂ  dans le cache. React utilisera pour ce faire une comparaison superficielle des arguments.

import {cache} from 'react';

const calculateNorm = cache((vector) => {
// ...
});

function MapMarker(props) {
// đŸš© ErronĂ© : les props sont un objet diffĂ©rent Ă  chaque rendu.
const length = calculateNorm(props);
// ...
}

function App() {
return (
<>
<MapMarker x={10} y={10} z={10} />
<MapMarker x={10} y={10} z={10} />
</>
);
}

Dans le cas ci-dessus, les deux MapMarker semblent faire exactement la mĂȘme chose et appeler calculateNorm avec les mĂȘmes valeurs {x: 10, y: 10, z:10}. MĂȘme si les objets contiennent des valeurs identiques, il ne s’agit pas d’une unique rĂ©fĂ©rence Ă  un mĂȘme objet, car chaque composant crĂ©e son propre objet props.

React appellera Object.is sur chaque argument pour vĂ©rifier l’existence dans le cache.

import {cache} from 'react';

const calculateNorm = cache((x, y, z) => {
// ...
});

function MapMarker(props) {
// ✅ Correct : passe des primitives Ă  la fonction mĂ©moĂŻsĂ©e
const length = calculateNorm(props.x, props.y, props.z);
// ...
}

function App() {
return (
<>
<MapMarker x={10} y={10} z={10} />
<MapMarker x={10} y={10} z={10} />
</>
);
}

Une façon de remĂ©dier Ă  ça consiste Ă  passer les dimensions du vecteur Ă  calculateNorm. Ça fonctionne parce que chaque dimension passĂ©e est une valeur primitive.

Vous pourriez aussi passer l’objet vecteur lui-mĂȘme comme prop au composant. Il vous faudrait toutefois passer le mĂȘme objet en mĂ©moire aux deux instances du composant.

import {cache} from 'react';

const calculateNorm = cache((vector) => {
// ...
});

function MapMarker(props) {
// ✅ Correct : passe le mĂȘme objet `vector`
const length = calculateNorm(props.vector);
// ...
}

function App() {
const vector = [10, 10, 10];
return (
<>
<MapMarker vector={vector} />
<MapMarker vector={vector} />
</>
);
}