cache - This feature is available in the latest Canary

React Server Components

cache é apenas para uso com Componentes React Server.

cache permite que você faça cache do resultado de uma busca ou computação de dados.

const cachedFn = cache(fn);

Referência

cache(fn)

Chame cache fora de quaisquer componentes para criar uma versão da função com cache.

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

const getMetrics = cache(calculateMetrics);

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

Quando getMetrics for chamado pela primeira vez com data, getMetrics chamará calculateMetrics(data) e armazenará o resultado no cache. Se getMetrics for chamado novamente com os mesmos data, ele retornará o resultado em cache em vez de chamar calculateMetrics(data) novamente.

Veja mais exemplos abaixo.

Parâmetros

  • fn: A função para a qual você deseja armazenar resultados em cache. fn pode receber quaisquer argumentos e retornar qualquer valor.

Retorna

cache retorna uma versão em cache de fn com a mesma assinatura de tipo. Ele não chama fn no processo.

Ao chamar cachedFn com argumentos fornecidos, ele primeiro verifica se um resultado em cache existe no cache. Se um resultado em cache existir, ele o retorna. Caso contrário, ele chama fn com os argumentos, armazena o resultado no cache e retorna o resultado. A única vez em que fn é chamado é quando há uma falha no cache.

Note

A otimização de armazenamento em cache de valores de retorno com base nas entradas é conhecida como memoization. Nos referimos à função retornada de cache como uma função memoizada.

Ressalvas

  • React invalidará o cache de todas as funções memoizadas para cada solicitação do servidor.
  • Cada chamada para cache cria uma nova função. Isso significa que chamar cache com a mesma função várias vezes retornará diferentes funções memoizadas que não compartilham o mesmo cache.
  • cachedFn também armazenará erros em cache. Se fn lançar um erro para determinados argumentos, ele será armazenado em cache e o mesmo erro será relançado quando cachedFn for chamado com esses mesmos argumentos.
  • cache é para uso somente em Componentes de Servidor.

Uso

Fazer cache de uma computação cara

Use cache para pular o trabalho duplicado.

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

const getUserMetrics = cache(calculateUserMetrics);

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

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

Se o mesmo objeto user for renderizado em Profile e TeamReport, os dois componentes podem compartilhar o trabalho e chamar calculateUserMetrics apenas uma vez para esse user.

Suponha que Profile seja renderizado primeiro. Ele chamará getUserMetrics e verificará se há um resultado em cache. Como é a primeira vez que getUserMetrics é chamado com esse user, haverá uma falha no cache. getUserMetrics então chamará calculateUserMetrics com esse user e gravará o resultado no cache.

Quando TeamReport renderizar sua lista de users e atingir o mesmo objeto user, ele chamará getUserMetrics e lerá o resultado do cache.

Pitfall

Chamar funções memoizadas diferentes lerá de caches diferentes.

Para acessar o mesmo cache, os componentes devem chamar a mesma função memoizada.

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

export function Temperature({cityData}) {
// 🚩 Incorreto: chamar 'cache' no componente cria um novo 'getWeekReport' para cada renderização
const getWeekReport = cache(calculateWeekReport);
const report = getWeekReport(cityData);
// ...
}
// Precipitation.js
import {cache} from 'react';
import {calculateWeekReport} from './report';

// 🚩 Incorreto: 'getWeekReport' só é acessível para o componente 'Precipitation'.
const getWeekReport = cache(calculateWeekReport);

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

No exemplo acima, Precipitation e Temperature cada um chama cache para criar uma nova função memoizada com sua própria busca de cache. Se ambos os componentes renderizarem para o mesmo cityData, eles farão um trabalho duplicado para chamar calculateWeekReport.

Além disso, Temperature cria uma nova função memoizada cada vez que o componente é renderizado, o que não permite nenhum compartilhamento de cache.

Para maximizar as ocorrências de cache e reduzir o trabalho, os dois componentes devem chamar a mesma função memoizada para acessar o mesmo cache. Em vez disso, defina a função memoizada em um módulo dedicado que pode ser import-ado em todos os componentes.

// 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);
// ...
}

Aqui, ambos os componentes chamam a mesma função memoizada exportada de ./getWeekReport.js para ler e gravar no mesmo cache.

Compartilhar um snapshot de dados

Para compartilhar um snapshot de dados entre componentes, chame cache com uma função de busca de dados como fetch. Quando vários componentes fazem a mesma busca de dados, apenas uma solicitação é feita e os dados retornados são armazenados em cache e compartilhados entre os componentes. Todos os componentes se referem ao mesmo snapshot de dados na renderização do servidor.

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);
// ...
}

Se AnimatedWeatherCard e MinimalWeatherCard renderizarem para a mesma cidade, eles receberão o mesmo snapshot de dados da função memoizada.

Se AnimatedWeatherCard e MinimalWeatherCard fornecerem argumentos diferentes de cidade para getTemperature, então fetchTemperature será chamado duas vezes e cada site de chamada receberá dados diferentes.

A cidade atua como uma chave de cache.

Note

Renderização assíncrona é suportada apenas para Componentes de Servidor.

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

Pré-carregar dados

Ao armazenar em cache uma busca de dados de longa duração, você pode iniciar o trabalho assíncrono antes de renderizar o componente.

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}) {
// ✅ Bom: começar a buscar os dados do usuário
getUser(id);
// ... some computational work
return (
<>
<Profile id={id} />
</>
);
}

Ao renderizar Page, o componente chama getUser, mas observe que ele não usa os dados retornados. Essa primeira chamada getUser inicia a consulta assíncrona do banco de dados que ocorre enquanto Page está fazendo outro trabalho computacional e renderizando os filhos.

Ao renderizar Profile, chamamos getUser novamente. Se a chamada inicial getUser já tiver retornado e armazenado em cache os dados do usuário, quando Profile pedir e esperar por esses dados, ele poderá simplesmente ler do cache sem exigir outra chamada de procedimento remoto. Se a solicitação de dados inicial não foi concluída, o pré-carregamento de dados nesse padrão reduz o atraso na busca de dados.

Deep Dive

Armazenamento em cache de trabalho assíncrono

Ao avaliar uma função assíncrona, você receberá uma Promise para esse trabalho. A promise contém o estado desse trabalho (pendente, cumprido, falhou) e seu eventual resultado resolvido.

Neste exemplo, a função assíncrona fetchData retorna uma promise que está aguardando o fetch.

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

const getData = cache(fetchData);

async function MyComponent() {
getData();
// ... some computational work
await getData();
// ...
}

Ao chamar getData pela primeira vez, a promise retornada de fetchData é armazenada em cache. As pesquisas subsequentes retornarão a mesma promise.

Observe que a primeira chamada getData não faz await, enquanto a segunda faz. await é um operador JavaScript que esperará e retornará o resultado resolvido da promise. A primeira chamada getData simplesmente inicia o fetch para armazenar em cache a promise para a segunda pesquisa getData.

Se na segunda chamada, a promise ainda estiver pendente, então await irá pausar pelo resultado. A otimização é que, enquanto esperamos pelo fetch, o React pode continuar com o trabalho computacional, reduzindo assim o tempo de espera para a segunda chamada.

Se a promise já estiver resolvida, seja para um erro ou para o resultado cumprido, await retornará esse valor imediatamente. Em ambos os resultados, há um benefício de desempenho.

Pitfall

Chamar uma função memoizada fora de um componente não usará o cache.
import {cache} from 'react';

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

// 🚩 Incorreto: chamar a função memoizada fora do componente não fará memoização.
getUser('demo-id');

async function DemoProfile() {
// ✅ Correto: `getUser` irá memoizar.
const user = await getUser('demo-id');
return <Profile user={user} />;
}

React apenas fornece acesso ao cache para a função memoizada em um componente. Ao chamar getUser fora de um componente, ele ainda avaliará a função, mas não lerá ou atualizará o cache.

Isso ocorre porque o acesso ao cache é fornecido por meio de um contexto, que só é acessível de um componente.

Deep Dive

Quando devo usar cache, memo ou useMemo?

Todas as APIs mencionadas oferecem memoização, mas a diferença é o que elas se destinam a memoizar, quem pode acessar o cache e quando seu cache é invalidado.

useMemo

Em geral, você deve usar useMemo para armazenar em cache uma computação cara em um Componente Cliente em várias renderizações. Como exemplo, para memoizar uma transformação de dados dentro de um componente.

'use client';

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

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

Neste exemplo, App renderiza dois WeatherReports com o mesmo registro. Mesmo que ambos os componentes façam o mesmo trabalho, eles não podem compartilhar o trabalho. O cache de useMemo é apenas local ao componente.

No entanto, useMemo garante que, se App renderizar novamente e o objeto record não mudar, cada instância do componente pulará o trabalho e usará o valor memoizado de avgTemp. useMemo só armazenará em cache a última computação de avgTemp com as dependências fornecidas.

cache

Em geral, você deve usar cache em Componentes de Servidor para memoizar o trabalho que pode ser compartilhado entre os componentes.

const cachedFetchReport = cache(fetchReport);

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

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

Reescrevendo o exemplo anterior para usar cache, neste caso a segunda instância de WeatherReport poderá pular o trabalho duplicado e ler do mesmo cache que a primeira WeatherReport. Outra diferença em relação ao exemplo anterior é que cache também é recomendado para memoizar buscas de dados, ao contrário de useMemo, que deve ser usado apenas para computações.

No momento, cache deve ser usado apenas em Componentes de Servidor e o cache será invalidado em solicitações de servidor.

memo

Você deve usar memo para impedir que um componente seja renderizado novamente se suas props não forem alteradas.

'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} />
</>
);
}

Neste exemplo, ambos os componentes MemoWeatherReport chamarão calculateAvg quando renderizados pela primeira vez. No entanto, se App renderizar novamente, sem alterações no record, nenhuma das props foi alterada e MemoWeatherReport não será renderizado novamente.

Em comparação com useMemo, memo memoiza a renderização do componente com base nas props vs. computações específicas. Semelhante a useMemo, o componente memoizado só armazena em cache a última renderização com os últimos valores de prop. Assim que as props mudam, o cache é invalidado e o componente é renderizado novamente.


solução de problemas

Minha função memoizada ainda é executada, embora eu a tenha chamado com os mesmos argumentos

Veja as armadilhas mencionadas anteriormente

Se nada do acima se aplicar, pode ser um problema com a forma como o React verifica se algo existe no cache.

Se seus argumentos não forem primitivos (ex. objetos, funções, arrays), certifique-se de passar a mesma referência de objeto.

Ao chamar uma função memoizada, o React procurará os argumentos de entrada para ver se um resultado já está em cache. React usará a igualdade superficial dos argumentos para determinar se há uma ocorrência de cache.

import {cache} from 'react';

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

function MapMarker(props) {
// 🚩 Incorreto: props é um objeto que muda a cada renderização.
const length = calculateNorm(props);
// ...
}

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

Nesse caso, os dois MapMarkers parecem estar fazendo o mesmo trabalho e chamando calculateNorm com o mesmo valor de {x: 10, y: 10, z:10}. Mesmo que os objetos contenham os mesmos valores, eles não são a mesma referência de objeto, pois cada componente cria seu próprio objeto props.

O React chamará Object.is na entrada para verificar se há uma ocorrência de cache.

import {cache} from 'react';

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

function MapMarker(props) {
// ✅ Correto: Passe primitivos para a função memoizada
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} />
</>
);
}

Uma maneira de resolver isso seria passar as dimensões do vetor para calculateNorm. Isso funciona porque as próprias dimensões são primitivos.

Outra solução pode ser passar o próprio objeto vetor como uma prop para o componente. Precisaremos passar o mesmo objeto para ambas as instâncias de componente.

import {cache} from 'react';

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

function MapMarker(props) {
// ✅ Correto: Passar o mesmo objeto `vector`
const length = calculateNorm(props.vector);
// ...
}

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