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.fnpode 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
cachecria uma nova função. Isso significa que chamarcachecom a mesma função várias vezes retornará diferentes funções memoizadas que não compartilham o mesmo cache. cachedFntambém armazenará erros em cache. Sefnlançar um erro para determinados argumentos, ele será armazenado em cache e o mesmo erro será relançado quandocachedFnfor 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 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
- 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 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} />
</>
);
}