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).
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âappelercache
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. Sifn
lĂšve une exception pour certains arguments, ce sera mis en cache, et la mĂȘme erreur sera levĂ©e lorsquecachedFn
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.
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.
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
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.
En détail
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 :
- Appeler des fonctions mémoïsées distinctes lira des caches distincts
- Appeler une fonction mĂ©moĂŻsĂ©e hors dâun composant nâutilisera pas le cache
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} />
</>
);
}