cache
permite que você armazene em cache o resultado de uma busca de dados ou computação.
const cachedFn = cache(fn);
Referência
cache(fn)
Chame cache
fora de qualquer componente 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
é chamado pela primeira vez com data
, getMetrics
chamará calculateMetrics(data)
e armazenará o resultado em cache. Se getMetrics
for chamado novamente com os mesmos data
, ele retornará o resultado em cache em vez de chamar calculateMetrics(data)
novamente.
Parâmetros
fn
: A função para a qual você deseja armazenar resultados em cache.fn
pode aceitar quaisquer argumentos e retornar qualquer valor.
Retornos
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 determinados argumentos, ele primeiro verifica se um resultado em cache existe. Se um resultado em cache existir, ele retorna o resultado. Se não, ele chama fn
com os argumentos, armazena o resultado em cache e retorna o resultado. A única vez que fn
é chamado é quando há uma falha no cache.
Ressalvas
- O React invalidará o cache para todas as funções memoizadas para cada solicitação do servidor.
- Cada chamada a
cache
cria uma nova função. Isso significa que chamarcache
com a mesma função várias vezes retornará diferentes funções memoizadas que não compartilham o mesmo cache. cachedFn
também armazenará em cache erros. Sefn
lançar um erro para determinados argumentos, ele será armazenado em cache, e o mesmo erro será relançado quandocachedFn
for chamado com esses mesmos argumentos.cache
é para uso apenas em Componentes do Servidor.
Uso
Armazenar em cache uma computação cara
Use cache
para evitar 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 ambos Profile
e TeamReport
, os dois componentes podem compartilhar trabalho e chamar calculateUserMetrics
apenas uma vez para aquele 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 aquele user
, haverá uma falha no cache. getUserMetrics
chamará então calculateUserMetrics
com aquele user
e gravará o resultado em cache.
Quando TeamReport
renderiza sua lista de users
e chega ao mesmo objeto user
, ele chamará getUserMetrics
e lerá o resultado do cache.
Compartilhar um instantâneo de dados
Para compartilhar um instantâneo de dados entre componentes, chame cache
com uma função de busca de dados como fetch
. Quando vários componentes realizam 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 referem-se ao mesmo instantâneo 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
ambos renderizarem para o mesmo city, eles receberão o mesmo instantâneo de dados da função memoizada.
Se AnimatedWeatherCard
e MinimalWeatherCard
fornecerem diferentes argumentos city para getTemperature
, então fetchTemperature
será chamado duas vezes e cada local de chamada receberá dados diferentes.
O city atua como uma chave de cache.
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: comece a buscar os dados do usuário
getUser(id);
// ... algum trabalho computacional
return (
<>
<Profile id={id} />
</>
);
}
Ao renderizar Page
, o componente chama getUser
mas note que não usa os dados retornados. Esta chamada inicial getUser
inicia a consulta assíncrona ao banco de dados que ocorre enquanto Page
está fazendo outro trabalho computacional e renderizando filhos.
Ao renderizar Profile
, chamamos getUser
novamente. Se a chamada inicial getUser
já retornou e armazenou em cache os dados do usuário, quando Profile
pede e aguarda esses dados, ele pode simplesmente ler do cache sem precisar de outra chamada remota. Se a solicitação de dados inicial não tiver sido concluída, a pré-carga de dados neste padrão reduz o atraso na busca de dados.
Deep Dive
Ao avaliar uma função assíncrona, você receberá uma Promise para esse trabalho. A promessa mantém o estado desse trabalho (pendente, cumprido, falhou) e seu eventual resultado resolvido.
Neste exemplo, a função assíncrona fetchData
retorna uma promessa que está aguardando o fetch
.
async function fetchData() {
return await fetch(`https://...`);
}
const getData = cache(fetchData);
async function MyComponent() {
getData();
// ... algum trabalho computacional
await getData();
// ...
}
Ao chamar getData
pela primeira vez, a promessa retornada de fetchData
é armazenada em cache. Busca subsequentes retornarão então a mesma promessa.
Note que a primeira chamada getData
não await
, enquanto a segunda sim. await
é um operador JavaScript que aguardará e retornará o resultado resolvido da promessa. A primeira chamada getData
simplesmente inicia o fetch
para armazenar a promessa para que a segunda getData
a consulte.
Se na segunda chamada a promessa ainda estiver pendente, então await
irá pausar para o resultado. A otimização é que enquanto aguardamos o fetch
, o React pode continuar com o trabalho computacional, reduzindo assim o tempo de espera para a segunda chamada.
Se a promessa 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.
Deep Dive
Todas as APIs mencionadas oferecem memoização, mas a diferença está no que elas pretendem 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 do Cliente entre 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 WeatherReport
s com o mesmo registro. Embora 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
re-renderizar 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 dadas.
cache
Em geral, você deve usar cache
em Componentes do Servidor para memoizar trabalho que pode ser compartilhado entre 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 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 só deve ser usado para computações.
No momento, cache
deve ser usado apenas em Componentes do Servidor e o cache será invalidado entre solicitações do servidor.
memo
Você deve usar memo
para evitar que um componente re-renderize se suas props não mudaram.
'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 forem renderizados pela primeira vez. No entanto, se App
re-renderizar, sem alterações no record
, nenhuma das props mudará e MemoWeatherReport
não re-renderizará.
Comparado a useMemo
, memo
memoiza a renderização do componente com base nas props em vez de computações específicas. Semelhante a useMemo
, o componente memoizado armazena em cache apenas a última renderização com os últimos valores de props. Assim que as props mudam, o cache é invalidado e o componente re-renderiza.
Solução de Problemas
Minha função memoizada ainda é executada mesmo que eu a tenha chamado com os mesmos argumentos
Veja as armadilhas mencionadas anteriormente
- Chamar diferentes funções memoizadas lerá de caches diferentes.
- Chamar uma função memoizada fora de um componente não usará o cache.
Se nenhuma das acima se aplicar, pode ser um problema com como o React verifica se algo existe em 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. O React usará igualdade rasa dos argumentos para determinar se há um acerto de cache.
import {cache} from 'react';
const calculateNorm = cache((vector) => {
// ...
});
function MapMarker(props) {
// 🚩 Errado: 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} />
</>
);
}
Neste caso, os dois MapMarker
s parecem estar fazendo o mesmo trabalho e chamando calculateNorm
com o mesmo valor de {x: 10, y: 10, z:10}
. Embora 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á um acerto de cache.
import {cache} from 'react';
const calculateNorm = cache((x, y, z) => {
// ...
});
function MapMarker(props) {
// ✅ Bom: 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 pode ser passar as dimensões do vetor para calculateNorm
. Isso funciona porque as dimensões são primitivos.
Outra solução pode ser passar o objeto vetor em si como um prop para o componente. Precisaremos passar o mesmo objeto para ambas as instâncias do componente.
import {cache} from 'react';
const calculateNorm = cache((vector) => {
// ...
});
function MapMarker(props) {
// ✅ Bom: Passe o mesmo objeto `vector`
const length = calculateNorm(props.vector);
// ...
}
function App() {
const vector = [10, 10, 10];
return (
<>
<MapMarker vector={vector} />
<MapMarker vector={vector} />
</>
);
}