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.
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.
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 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á erros em cache. 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 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.
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.
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
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.
Deep Dive
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 WeatherReport
s 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
- Chamar funções memoizadas diferentes lerá de caches diferentes.
- Chamar uma função memoizada fora de um componente não usará o cache.
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 MapMarker
s 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} />
</>
);
}