Movatterモバイル変換


[0]ホーム

URL:


Is this page useful?

React Server Components

cache is only for use withReact Server Components.

cache lets you cache the result of a data fetch or computation.

constcachedFn =cache(fn);

Reference

cache(fn)

Callcache outside of any components to create a version of the function with caching.

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

constgetMetrics =cache(calculateMetrics);

functionChart({data}){
constreport =getMetrics(data);
// ...
}

WhengetMetrics is first called withdata,getMetrics will callcalculateMetrics(data) and store the result in cache. IfgetMetrics is called again with the samedata, it will return the cached result instead of callingcalculateMetrics(data) again.

See more examples below.

Parameters

  • fn: The function you want to cache results for.fn can take any arguments and return any value.

Returns

cache returns a cached version offn with the same type signature. It does not callfn in the process.

When callingcachedFn with given arguments, it first checks if a cached result exists in the cache. If a cached result exists, it returns the result. If not, it callsfn with the arguments, stores the result in the cache, and returns the result. The only timefn is called is when there is a cache miss.

Note

The optimization of caching return values based on inputs is known asmemoization. We refer to the function returned fromcache as a memoized function.

Caveats

  • React will invalidate the cache for all memoized functions for each server request.
  • Each call tocache creates a new function. This means that callingcache with the same function multiple times will return different memoized functions that do not share the same cache.
  • cachedFn will also cache errors. Iffn throws an error for certain arguments, it will be cached, and the same error is re-thrown whencachedFn is called with those same arguments.
  • cache is for use inServer Components only.

Usage

Cache an expensive computation

Usecache to skip duplicate work.

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

constgetUserMetrics =cache(calculateUserMetrics);

functionProfile({user}){
constmetrics =getUserMetrics(user);
// ...
}

functionTeamReport({users}){
for(letuserinusers){
constmetrics =getUserMetrics(user);
// ...
}
// ...
}

If the sameuser object is rendered in bothProfile andTeamReport, the two components can share work and only callcalculateUserMetrics once for thatuser.

AssumeProfile is rendered first. It will callgetUserMetrics, and check if there is a cached result. Since it is the first timegetUserMetrics is called with thatuser, there will be a cache miss.getUserMetrics will then callcalculateUserMetrics with thatuser and write the result to cache.

WhenTeamReport renders its list ofusers and reaches the sameuser object, it will callgetUserMetrics and read the result from cache.

IfcalculateUserMetrics can be aborted by passing anAbortSignal, you can usecacheSignal() to cancel the expensive computation if React has finished rendering.calculateUserMetrics may already handle cancellation internally by usingcacheSignal directly.

Pitfall

Calling different memoized functions will read from different caches.

To access the same cache, components must call the same memoized function.

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

exportfunctionTemperature({cityData}){
// 🚩 Wrong: Calling `cache` in component creates new `getWeekReport` for each render
constgetWeekReport =cache(calculateWeekReport);
constreport =getWeekReport(cityData);
// ...
}
// Precipitation.js
import{cache}from'react';
import{calculateWeekReport}from'./report';

// 🚩 Wrong: `getWeekReport` is only accessible for `Precipitation` component.
constgetWeekReport =cache(calculateWeekReport);

exportfunctionPrecipitation({cityData}){
constreport =getWeekReport(cityData);
// ...
}

In the above example,Precipitation andTemperature each callcache to create a new memoized function with their own cache look-up. If both components render for the samecityData, they will do duplicate work to callcalculateWeekReport.

In addition,Temperature creates anew memoized function each time the component is rendered which doesn’t allow for any cache sharing.

To maximize cache hits and reduce work, the two components should call the same memoized function to access the same cache. Instead, define the memoized function in a dedicated module that can beimport-ed across components.

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

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

exportdefaultfunctionTemperature({cityData}){
constreport =getWeekReport(cityData);
// ...
}
// Precipitation.js
importgetWeekReportfrom'./getWeekReport';

exportdefaultfunctionPrecipitation({cityData}){
constreport =getWeekReport(cityData);
// ...
}

Here, both components call thesame memoized function exported from./getWeekReport.js to read and write to the same cache.

Share a snapshot of data

To share a snapshot of data between components, callcache with a data-fetching function likefetch. When multiple components make the same data fetch, only one request is made and the data returned is cached and shared across components. All components refer to the same snapshot of data across the server render.

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

constgetTemperature =cache(async(city)=>{
returnawaitfetchTemperature(city);
});

asyncfunctionAnimatedWeatherCard({city}){
consttemperature =awaitgetTemperature(city);
// ...
}

asyncfunctionMinimalWeatherCard({city}){
consttemperature =awaitgetTemperature(city);
// ...
}

IfAnimatedWeatherCard andMinimalWeatherCard both render for the samecity, they will receive the same snapshot of data from thememoized function.

IfAnimatedWeatherCard andMinimalWeatherCard supply differentcity arguments togetTemperature, thenfetchTemperature will be called twice and each call site will receive different data.

Thecity acts as a cache key.

Note

Asynchronous rendering is only supported for Server Components.

asyncfunctionAnimatedWeatherCard({city}){
consttemperature =awaitgetTemperature(city);
// ...
}

To render components that use asynchronous data in Client Components, seeuse() documentation.

Preload data

By caching a long-running data fetch, you can kick off asynchronous work prior to rendering the component.

constgetUser =cache(async(id)=>{
returnawaitdb.user.query(id);
});

asyncfunctionProfile({id}){
constuser =await getUser(id);
return(
<section>
<imgsrc={user.profilePic}/>
<h2>{user.name}</h2>
</section>
);
}

functionPage({id}){
// ✅ Good: start fetching the user data
getUser(id);
// ... some computational work
return(
<>
<Profileid={id}/>
</>
);
}

When renderingPage, the component callsgetUser but note that it doesn’t use the returned data. This earlygetUser call kicks off the asynchronous database query that occurs whilePage is doing other computational work and rendering children.

When renderingProfile, we callgetUser again. If the initialgetUser call has already returned and cached the user data, whenProfileasks and waits for this data, it can simply read from the cache without requiring another remote procedure call. If the initial data request hasn’t been completed, preloading data in this pattern reduces delay in data-fetching.

Deep Dive

Caching asynchronous work

When evaluating anasynchronous function, you will receive aPromise for that work. The promise holds the state of that work (pending,fulfilled,failed) and its eventual settled result.

In this example, the asynchronous functionfetchData returns a promise that is awaiting thefetch.

asyncfunctionfetchData(){
returnawaitfetch(`https://...`);
}

constgetData =cache(fetchData);

asyncfunctionMyComponent(){
getData();
// ... some computational work
awaitgetData();
// ...
}

In callinggetData the first time, the promise returned fromfetchData is cached. Subsequent look-ups will then return the same promise.

Notice that the firstgetData call does notawait whereas thesecond does.await is a JavaScript operator that will wait and return the settled result of the promise. The firstgetData call simply initiates thefetch to cache the promise for the secondgetData to look-up.

If by thesecond call the promise is stillpending, thenawait will pause for the result. The optimization is that while we wait on thefetch, React can continue with computational work, thus reducing the wait time for thesecond call.

If the promise is already settled, either to an error or thefulfilled result,await will return that value immediately. In both outcomes, there is a performance benefit.

Pitfall

Calling a memoized function outside of a component will not use the cache.
import{cache}from'react';

constgetUser =cache(async(userId)=>{
returnawaitdb.user.query(userId);
});

// 🚩 Wrong: Calling memoized function outside of component will not memoize.
getUser('demo-id');

asyncfunctionDemoProfile(){
// ✅ Good: `getUser` will memoize.
constuser =awaitgetUser('demo-id');
return<Profileuser={user}/>;
}

React only provides cache access to the memoized function in a component. When callinggetUser outside of a component, it will still evaluate the function but not read or update the cache.

This is because cache access is provided through acontext which is only accessible from a component.

Deep Dive

When should I usecache,memo, oruseMemo?

All mentioned APIs offer memoization but the difference is what they’re intended to memoize, who can access the cache, and when their cache is invalidated.

useMemo

In general, you should useuseMemo for caching an expensive computation in a Client Component across renders. As an example, to memoize a transformation of data within a component.

'use client';

functionWeatherReport({record}){
constavgTemp =useMemo(()=>calculateAvg(record),record);
// ...
}

functionApp(){
constrecord =getRecord();
return(
<>
<WeatherReportrecord={record}/>
<WeatherReportrecord={record}/>
</>
);
}

In this example,App renders twoWeatherReports with the same record. Even though both components do the same work, they cannot share work.useMemo’s cache is only local to the component.

However,useMemo does ensure that ifApp re-renders and therecord object doesn’t change, each component instance would skip work and use the memoized value ofavgTemp.useMemo will only cache the last computation ofavgTemp with the given dependencies.

cache

In general, you should usecache in Server Components to memoize work that can be shared across components.

constcachedFetchReport =cache(fetchReport);

functionWeatherReport({city}){
constreport =cachedFetchReport(city);
// ...
}

functionApp(){
constcity ="Los Angeles";
return(
<>
<WeatherReport city={city} />
<WeatherReport city={city} />
</>
);
}

Re-writing the previous example to usecache, in this case thesecond instance ofWeatherReport will be able to skip duplicate work and read from the same cache as thefirstWeatherReport. Another difference from the previous example is thatcache is also recommended formemoizing data fetches, unlikeuseMemo which should only be used for computations.

At this time,cache should only be used in Server Components and the cache will be invalidated across server requests.

memo

You should usememo to prevent a component re-rendering if its props are unchanged.

'use client';

functionWeatherReport({record}){
constavgTemp =calculateAvg(record);
// ...
}

constMemoWeatherReport =memo(WeatherReport);

functionApp(){
constrecord =getRecord();
return(
<>
<MemoWeatherReportrecord={record}/>
<MemoWeatherReportrecord={record}/>
</>
);
}

In this example, bothMemoWeatherReport components will callcalculateAvg when first rendered. However, ifApp re-renders, with no changes torecord, none of the props have changed andMemoWeatherReport will not re-render.

Compared touseMemo,memo memoizes the component render based on props vs. specific computations. Similar touseMemo, the memoized component only caches the last render with the last prop values. Once the props change, the cache invalidates and the component re-renders.


Troubleshooting

My memoized function still runs even though I’ve called it with the same arguments

See prior mentioned pitfalls

If none of the above apply, it may be a problem with how React checks if something exists in cache.

If your arguments are notprimitives (ex. objects, functions, arrays), ensure you’re passing the same object reference.

When calling a memoized function, React will look up the input arguments to see if a result is already cached. React will use shallow equality of the arguments to determine if there is a cache hit.

import{cache}from'react';

constcalculateNorm =cache((vector)=>{
// ...
});

functionMapMarker(props){
// 🚩 Wrong: props is an object that changes every render.
constlength =calculateNorm(props);
// ...
}

functionApp(){
return(
<>
<MapMarkerx={10}y={10}z={10}/>
<MapMarkerx={10}y={10}z={10}/>
</>
);
}

In this case the twoMapMarkers look like they’re doing the same work and callingcalculateNorm with the same value of{x: 10, y: 10, z:10}. Even though the objects contain the same values, they are not the same object reference as each component creates its ownprops object.

React will callObject.is on the input to verify if there is a cache hit.

import{cache}from'react';

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

functionMapMarker(props){
// ✅ Good: Pass primitives to memoized function
constlength =calculateNorm(props.x,props.y,props.z);
// ...
}

functionApp(){
return(
<>
<MapMarkerx={10}y={10}z={10}/>
<MapMarkerx={10}y={10}z={10}/>
</>
);
}

One way to address this could be to pass the vector dimensions tocalculateNorm. This works because the dimensions themselves are primitives.

Another solution may be to pass the vector object itself as a prop to the component. We’ll need to pass the same object to both component instances.

import{cache}from'react';

constcalculateNorm =cache((vector)=>{
// ...
});

functionMapMarker(props){
// ✅ Good: Pass the same `vector` object
constlength =calculateNorm(props.vector);
// ...
}

functionApp(){
constvector =[10,10,10];
return(
<>
<MapMarkervector={vector}/>
<MapMarkervector={vector}/>
</>
);
}


[8]ページ先頭

©2009-2025 Movatter.jp