Menu

Hook useMemo no React: guarde em cache cálculos caros

useMemo guarda em cache o resultado de um cálculo entre renderizações e só o recalcula quando as dependências mudam. Aprenda a sintaxe, como manter um objeto estável para um filho com memo e quando o useMemo não vale a pena.

Esta página tem editores executáveis - edite, execute e veja a saída na hora.

useMemo guarda em cache o resultado de um cálculo entre renderizações. O React executa a sua função na primeira renderização, guarda o resultado e devolve esse mesmo resultado nas renderizações seguintes até que um dos valores do array de dependências mude.

Abra o Console e alterne o tema algumas vezes: nada é registrado, e a alternância parece instantânea. Troque de aba e aparece filtering for active, com uma pequena pausa, porque tab é uma dependência. Agora substitua a linha do useMemo por const visible = filterTodos(todos, tab); e alterne o tema de novo: cada clique registra e trava por 100 ms.

A sintaxe

const cachedValue = useMemo(calculateValue, dependencies);

const visible = useMemo(() => filterTodos(todos, tab), [todos, tab]);
  • calculateValue é uma função sem argumentos que retorna o valor que você quer guardar em cache. O React a chama durante a renderização.
  • dependencies é a lista de todos os valores do componente que a função lê: props, estado e variáveis declaradas no corpo do componente.
  • A cada renderização, o React compara cada dependência com o valor dela na renderização anterior usando Object.is. Se todas coincidem, ele retorna o valor em cache sem chamar a sua função. Se alguma difere, ele chama a função e guarda o novo resultado.

O useMemo é um hook, então segue as regras dos hooks: chame-o no nível superior do componente, não dentro de um loop ou de uma condição.

As dependências precisam estar completas

Deixe um valor de fora do array e o cache continua entregando um resultado calculado com o valor antigo dele. Aqui o total lê qty, mas só lista price:

Clique em Quantity: ela vai para 2, mas o total fica em 10 e nada é registrado. Clique em Price e o total salta para 22, porque só então o cálculo executa de novo, agora lendo o qty atual. Adicione qty ao array e os dois botões atualizam o total. A regra de lint react-hooks/exhaustive-deps pega esse erro em um projeto real.

O erro oposto é uma dependência que é nova a cada renderização, como um objeto criado no corpo do componente. Ela nunca coincide com o valor anterior, então o cache nunca é usado:

const filters = { tab, query }; // a new object every render
const visible = useMemo(() => applyFilters(todos, filters), [todos, filters]); // recomputes every time

// List the primitive values instead
const visible = useMemo(() => applyFilters(todos, { tab, query }), [todos, tab, query]);

Mantendo um objeto estável para um filho com memo

O segundo motivo para recorrer ao useMemo não tem nada a ver com cálculos lentos. Um filho envolvido em memo só pula a renderização quando todas as props são as mesmas da última vez. Um literal de objeto escrito no corpo do componente é um objeto novo a cada renderização, então anula o memo mesmo quando o conteúdo não mudou. O useMemo mantém o mesmo objeto até as entradas dele mudarem.

Digite no campo de anotação e Chart fica quieto, enquanto Switch unit registra uma renderização. Substitua a chamada do useMemo por um simples const options = { unit, max: unit === 'km' ? 100 : 62 }; e cada tecla na anotação registra render Chart.

O mesmo vale para um efeito que depende de um objeto. Se um efeito lista options nas dependências e options é recriado a cada renderização, o efeito executa depois de toda renderização. O useMemo resolve isso, mas a solução mais simples muitas vezes é criar o objeto dentro do efeito e depender dos valores primitivos:

useEffect(() => {
    const options = { serverUrl, roomId };
    const connection = createConnection(options);
    connection.connect();
    return () => connection.disconnect();
}, [serverUrl, roomId]);

Quando não usar o useMemo

A maioria dos cálculos em um componente é barata: somar números, juntar duas strings, filtrar algumas dezenas de itens. Guardar isso em cache não economiza quase nada: o React ainda guarda o valor e compara as dependências a cada renderização, e o código fica mais difícil de ler.

// No useMemo needed
const fullName = first + ' ' + last;
const doneCount = todos.filter((t) => t.done).length;

Para descobrir se algo é lento, meça. Envolva o cálculo em console.time('filter') e console.timeEnd('filter'), execute a interação e veja o número. Como referência aproximada, trabalho que leva um milissegundo ou mais na sua máquina é candidato, lembrando que os aparelhos dos seus usuários podem ser mais lentos. Antes de recorrer ao useMemo, veja também se o trabalho deveria estar na renderização: dados que nunca mudam podem ser calculados uma vez fora do componente, e estado que só uma parte pequena da página usa pode ir para um componente menor (veja renderização).

useMemo vs useEffect para valores derivados

Um padrão comum é calcular um valor em um efeito e guardá-lo no estado:

// Two renders per change, and the first one has stale data
const [visible, setVisible] = useState([]);
useEffect(() => {
    setVisible(filterTodos(todos, tab));
}, [todos, tab]);

// One render, always in sync
const visible = useMemo(() => filterTodos(todos, tab), [todos, tab]);

A versão com efeito renderiza uma vez com o visible antigo, depois executa o efeito, define o estado e renderiza de novo. É uma renderização extra, e na primeira delas a lista não corresponde à aba. Calcular durante a renderização com useMemo (ou sem hook nenhum, se o trabalho for barato) dá o valor certo na mesma renderização. Efeitos servem para sincronizar com coisas fora do React, não para derivar uma parte do estado de outra.

Uma dica de performance, não uma garantia

O useMemo diz ao React que ele pode reutilizar um resultado. Não promete que vai. O React mantém o valor em cache na operação normal, mas pode descartá-lo, por exemplo quando você edita o arquivo durante o desenvolvimento, ou quando um componente suspende durante a primeira montagem. Escreva o código para que ele continue correto se o cálculo executar de novo: a função precisa ser pura, sem efeitos colaterais como requisições, assinaturas ou mudanças em variáveis fora dela. Efeitos colaterais ficam em um event handler ou em um efeito.

Em desenvolvimento, com StrictMode, o React chama a sua função de cálculo duas vezes para ajudar você a encontrar código impuro. A prévia aqui roda como um build de produção, então você vê cada log filtering for uma vez.

useMemo e o React Compiler

O React Compiler 1.0 é uma ferramenta de build que analisa os seus componentes e adiciona memoização automaticamente, guardando em cache cálculos e JSX nos lugares em que useMemo, useCallback e memo ajudariam. Em um projeto que o usa, você escreve menos disso à mão. O useMemo manual continua funcionando junto com o compilador, continua sendo a ferramenta quando o compilador não está configurado, e você vai encontrá-lo na maior parte do código React existente.

Perguntas frequentes

O que o useMemo faz no React?

Ele chama a sua função de cálculo na primeira renderização, guarda o resultado e, nas renderizações seguintes, retorna o resultado guardado enquanto todos os valores do array de dependências forem os mesmos da última vez (comparados com Object.is).

Quando devo usar o useMemo?

Quando um cálculo é perceptivelmente lento e as entradas dele mudam raramente, ou quando você precisa que um objeto ou array mantenha a mesma referência entre renderizações porque um filho com memo ou um efeito depende dele. Para trabalho barato, dispense.

Qual é a diferença entre useMemo e useCallback?

O useMemo guarda em cache o valor que a sua função retorna. O useCallback guarda em cache a própria função. useCallback(fn, deps) é o mesmo que useMemo(() => fn, deps).

O useMemo garante que o valor nunca é recalculado?

Não. Ele é uma dica de performance. O React pode descartar o valor em cache em alguns casos, por exemplo em desenvolvimento quando você edita o arquivo, então o seu código precisa continuar correto se o cálculo executar de novo.

Ainda preciso do useMemo com o React Compiler?

O React Compiler pode adicionar memoização automaticamente quando o seu build o usa. Sem o compilador, ou nos casos que ele pula, o useMemo manual continua funcionando do mesmo jeito e ainda é comum em código existente.

Ilustração das linguagens de programação do Coddy

Aprenda a programar com o Coddy

COMEÇAR