Menu

React.memo: pule re-renderizações quando as props são iguais

memo envolve um componente para que o React pule a nova renderização dele quando as props são superficialmente iguais às da última vez. Entenda quando ele ajuda, por que objetos e funções novos nas props o anulam e o que ele não impede.

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

O memo permite que um componente pule a nova renderização quando as props são as mesmas da última vez. Envolva um componente em memo(...) e, quando o pai renderizar, o React primeiro compara cada prop nova com a antiga; se todas forem iguais, ele reutiliza o resultado anterior em vez de chamar o componente de novo.

Digite no input: ele acompanha, e o Console mostra render SlowList só uma vez, da primeira renderização. Remova o wrapper memo( (e o parêntese de fechamento dele) e digite de novo: cada tecla agora espera 150 ms pela lista e registra uma renderização.

A sintaxe

import { memo } from 'react';

const MemoizedComponent = memo(SomeComponent, arePropsEqual?);
  • SomeComponent é qualquer componente de função. O estilo usual é envolver a definição diretamente: const Card = memo(function Card(props) { ... }). Dar nome à função interna mantém o nome no React DevTools e nas mensagens de erro.
  • arePropsEqual é opcional e raramente necessário (veja abaixo).
  • O resultado é um componente que você renderiza como qualquer outro.

Por padrão, o React compara as props antigas e novas de forma superficial: ele confere cada prop com Object.is. Strings, números e booleanos são comparados pelo valor, então title="Hi" é igual a title="Hi". Objetos, arrays e funções são comparados pela referência: dois objetos com o mesmo conteúdo continuam sendo objetos diferentes.

No exemplo acima, items é declarado fora do componente, então é o mesmo array em toda renderização, e a comparação passa.

Objetos e funções novos anulam o memo

Qualquer coisa escrita como literal dentro do corpo do pai é criada de novo a cada renderização: { name: 'Ada' }, [1, 2, 3], () => save(). Passe um desses para um componente com memo e a prop nunca é igual à anterior.

Cada clique registra render Badge object prop, mas nunca a versão com string. Há três formas de corrigir o caso do objeto:

  • Passe valores primitivos. name="Ada" em vez de user={{ name: 'Ada' }} quando o filho só precisa de um ou dois campos.
  • Tire as constantes do componente. Um objeto que nunca muda pode viver no nível do módulo, como items no primeiro exemplo.
  • Guarde em cache. Envolva objetos e arrays montados a partir de props ou estado em useMemo, e funções em useCallback.

O children tem o mesmo problema. <Card><p>Hello</p></Card> cria um objeto de elemento <p> novo a cada renderização, então um Card com memo que recebe children renderiza de novo toda vez.

O que o memo não impede

O memo só olha para as props que vêm do pai. Um componente memoizado ainda renderiza quando:

  • o próprio estado muda, pelo próprio useState ou useReducer;
  • um context que ele lê muda, pelo useContext.

Clique em Unrelated e Panel é pulado. Troque o tema e ele renderiza, porque lê o context. Clique em Like e ele renderiza, porque o próprio estado mudou. Esse é o comportamento esperado: o memo serve para pular trabalho quando nada de que o componente depende mudou, e context e estado são coisas de que ele depende.

Uma função de comparação própria

O segundo argumento substitui a comparação superficial. Ele recebe as props anteriores e as próximas e retorna true quando elas devem ser tratadas como iguais, o que significa que o React pula a renderização.

const MemoChart = memo(Chart, (prev, next) => {
    return (
        prev.points.length === next.points.length &&
        prev.points.every((p, i) => p === next.points[i]) &&
        prev.color === next.color &&
        prev.onSelect === next.onSelect
    );
});

Você raramente precisa disso, e é fácil errar. Se a função ignora uma prop, o componente continua mostrando o valor antigo dessa prop. Ignorar uma prop de função é o pior caso: o componente pulado continua chamando a função antiga, que lê estado antigo. Compare todas as props e não faça comparações profundas em dados grandes, porque aí a comparação custa mais do que a renderização que ela economiza. Props estáveis com useMemo e useCallback são quase sempre a melhor correção.

Conferindo se o memo funciona

A verificação mais rápida é a usada nesta página: um console.log no topo do componente. Se ele imprimir quando você esperava que fosse pulado, registre as props também e compare-as com as anteriores para achar a que mudou. Em um projeto real, o React DevTools ajuda mais: ligue "Highlight updates when components render" nas configurações dele para ver quais componentes piscam a cada interação, e a aba Profiler grava cada renderização e, com a configuração correspondente ligada, por que cada componente renderizou.

Duas coisas parecem falha do memo, mas não são:

  • A primeira renderização sempre acontece. O memo só pula renderizações depois que o componente renderizou uma vez com aquelas props.
  • Builds de desenvolvimento podem renderizar mais. Com StrictMode, o React renderiza cada componente duas vezes em desenvolvimento para pegar código impuro. A prévia aqui roda como um build de produção, então cada linha de log acima significa uma renderização real.

Quando usar o memo

O memo vale a pena quando um componente é lento de renderizar e o pai dele renderiza com frequência com as mesmas props para ele: uma lista longa ou um gráfico ao lado de um input, uma barra lateral dentro de uma página que atualiza a cada segundo. Para componentes que renderizam rápido, ou cujas props mudam toda vez de qualquer jeito, ele adiciona uma comparação e não ganha nada. Antes de memoizar, veja se dá para reorganizar, movendo o estado para baixo ou passando JSX como children, como mostra a página sobre renderização.

O React Compiler 1.0, se o seu projeto o usa, memoiza componentes e as props deles automaticamente no build, o que torna desnecessária a maioria das chamadas manuais de memo. Sem ele, o memo funciona exatamente como descrito aqui e continua comum em código React.

Perguntas frequentes

O que o React.memo faz?

Ele retorna uma versão memoizada de um componente. Quando o pai renderiza, o React compara as novas props com as anteriores e pula a renderização do componente se todas as props forem iguais segundo Object.is.

Por que meu componente com memo continua renderizando?

Normalmente porque uma prop é um objeto, array ou função novo criado durante a renderização do pai, então nunca é igual ao anterior. O componente também renderiza quando o próprio estado muda ou quando um context que ele lê muda, com memo ou sem.

Qual é a diferença entre React.memo e useMemo?

O memo envolve um componente e pula a renderização dele. O useMemo é um hook dentro de um componente que guarda em cache um valor calculado. Eles costumam trabalhar juntos: o useMemo mantém estável uma prop de objeto para que o memo possa pular.

Devo envolver todo componente em memo?

Não. A maioria das renderizações é barata, e a comparação das props tem o próprio custo. Use-o em componentes lentos de renderizar que recebem com frequência as mesmas props.

O memo funciona com children?

Raramente. O JSX passado como children é um objeto novo a cada renderização, então um componente com memo que recebe children quase sempre renderiza de novo.

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

Aprenda a programar com o Coddy

COMEÇAR