Performance em React: memo, useMemo, useCallback e Profiler na Prática Já leu

Performance em React: memo, useMemo, useCallback e Profiler React é declarativo e eficiente por padrão, mas aplicações grandes podem sofrer com renderizações desnecessárias. Nesta aula, você aprenderá as ferramentas essenciais para identificar e otimizar gargalos de performance em seus componentes. Abordaremos desde técnicas de memorização até ferramentas de diagnóstico. React.memo: Evitando Renderizações Desnecessárias React.memo é um componente de ordem superior (HOC) que memoriza um componente funcional. Se as props não mudarem, o componente não é renderizado novamente. Essa é a primeira linha de defesa contra renderizações desperdiçadas. Cuidado com objetos e funções: memo compara props superficialmente. Se você passa um objeto ou função nova a cada render, memo não funciona. Para esses casos, use a função de comparação customizada ou combine com useCallback. useMemo: Cachear Cálculos Pesados useMemo é um hook que memoriza o resultado de uma computação. Use-o quando você tem operações custosas que não precisam rodar a cada render: filtros complexos, ordenações, ou cálculos matemáticos intensivos. O array

Performance em React: memo, useMemo, useCallback e Profiler

React é declarativo e eficiente por padrão, mas aplicações grandes podem sofrer com renderizações desnecessárias. Nesta aula, você aprenderá as ferramentas essenciais para identificar e otimizar gargalos de performance em seus componentes. Abordaremos desde técnicas de memorização até ferramentas de diagnóstico.

React.memo: Evitando Renderizações Desnecessárias

React.memo é um componente de ordem superior (HOC) que memoriza um componente funcional. Se as props não mudarem, o componente não é renderizado novamente. Essa é a primeira linha de defesa contra renderizações desperdiçadas.

// Sem memo - renderiza sempre que o pai renderiza
function UserCard({ name, email }) {
  console.log('UserCard renderizado');
  return (
    <div>
      <h3>{name}</h3>
      <p>{email}</p>
    </div>
  );
}

// Com memo - renderiza apenas se name ou email mudarem
const UserCard = React.memo(function UserCard({ name, email }) {
  console.log('UserCard renderizado');
  return (
    <div>
      <h3>{name}</h3>
      <p>{email}</p>
    </div>
  );
});

// Componente pai
function App() {
  const [count, setCount] = useState(0);

  return (
    <>
      <button onClick={() => setCount(count + 1)}>
        Cliques: {count}
      </button>
      {/* UserCard não renderiza ao clicar o botão */}
      <UserCard name="João" email="joao@email.com" />
    </>
  );
}

Cuidado com objetos e funções: memo compara props superficialmente. Se você passa um objeto ou função nova a cada render, memo não funciona. Para esses casos, use a função de comparação customizada ou combine com useCallback.

// Problema: objeto novo a cada render
<UserCard user={{ name: "João", email: "j@email.com" }} /> // Sempre renderiza

// Solução: memoizar o objeto
const user = useMemo(() => ({ name: "João", email: "j@email.com" }), []);
<UserCard user={user} /> // Renderiza apenas uma vez

useMemo: Cachear Cálculos Pesados

useMemo é um hook que memoriza o resultado de uma computação. Use-o quando você tem operações custosas que não precisam rodar a cada render: filtros complexos, ordenações, ou cálculos matemáticos intensivos.

function ProductList({ products, filter }) {
  // Sem useMemo: filtro executado a cada render
  const filtered = products.filter(p => p.category === filter);

  return (
    <ul>
      {filtered.map(p => <li key={p.id}>{p.name}</li>)}
    </ul>
  );
}

// Com useMemo: filtro executado apenas quando products ou filter mudam
function ProductList({ products, filter }) {
  const filtered = useMemo(
    () => products.filter(p => p.category === filter),
    [products, filter] // Dependências
  );

  return (
    <ul>
      {filtered.map(p => <li key={p.id}>{p.name}</li>)}
    </ul>
  );
}

O array de dependências é crítico. Se omitir uma dependência, você terá dados desatualizados. Se incluir tudo, useMemo não traz benefício. A regra prática: inclua tudo que o callback usa e vem do escopo externo.

// Exemplo real com múltiplas dependências
const stats = useMemo(() => {
  return {
    total: products.reduce((sum, p) => sum + p.price, 0),
    count: products.length,
    avgPrice: products.reduce((sum, p) => sum + p.price, 0) / products.length
  };
}, [products]); // products é a única dependência necessária

useCallback: Memorizar Funções

useCallback é para quando você precisa passar uma função como prop para um componente memo. Sem ele, a função é recriada a cada render, invalidando a memorização do componente filho.

// Problema: handleClick recriada a cada render
function Parent() {
  const [count, setCount] = useState(0);

  const handleClick = () => {
    setCount(count + 1);
  };

  // Child sempre renderiza porque handleClick é nova
  return <Child onClick={handleClick} />;
}

// Solução: useCallback
function Parent() {
  const [count, setCount] = useState(0);

  const handleClick = useCallback(() => {
    setCount(c => c + 1); // Use função anterior de estado
  }, []); // Sem dependências, handleClick nunca muda

  return <Child onClick={handleClick} />;
}

const Child = React.memo(function Child({ onClick }) {
  console.log('Child renderizado');
  return <button onClick={onClick}>Clique</button>;
});

Caso de uso real: callbacks em listas de itens. Sem useCallback, cada item renderiza quando o pai muda.

function TodoList({ todos, onRemove }) {
  const handleRemove = useCallback((id) => {
    onRemove(id);
  }, [onRemove]); // Depende de onRemove do pai

  return (
    <ul>
      {todos.map(todo => (
        <TodoItem 
          key={todo.id} 
          todo={todo} 
          onRemove={handleRemove}
        />
      ))}
    </ul>
  );
}

const TodoItem = React.memo(function TodoItem({ todo, onRemove }) {
  return (
    <li>
      {todo.text}
      <button onClick={() => onRemove(todo.id)}>X</button>
    </li>
  );
});

React DevTools Profiler: Diagnóstico Profissional

Não otimize sem dados. O Profiler do React DevTools mostra exatamente quais componentes renderizam, por quanto tempo e por quê. Isso diferencia um otimizador amador de um profissional.

Acesse o Profiler na aba "Profiler" do React DevTools. Clique em "Record", interaja com sua aplicação, depois pare a gravação. Você verá cada render: tempo de render, causas (prop ou state mudou), e comparação antes/depois.

// Componente para análise
function Dashboard() {
  const [selected, setSelected] = useState(null);
  const [data, setData] = useState([]);

  useEffect(() => {
    // Simula fetch
    setTimeout(() => setData([...Array(100)].map((_, i) => ({ id: i }))), 1000);
  }, []);

  return (
    <>
      <button onClick={() => setSelected(selected ? null : 1)}>
        Toggle (causa re-render de toda árvore)
      </button>
      <MemoizedChart data={data} />
      <ItemList items={data} />
    </>
  );
}

const MemoizedChart = React.memo(function Chart({ data }) {
  // Se não tiver memo, renderiza desnecessariamente
  return <div>Gráfico com {data.length} itens</div>;
});

Dica profissional: Procure por "Render for no reason" no Profiler. Se um componente memo renderiza quando sua prop não mudou, está havendo renderização de pai desnecessária. Suba em pais e aplique técnicas de otimização em cascata.

A regra de ouro: otimize apenas o que o Profiler evidenciar como problema. Prematura optimization é raiz de todo mal.

Conclusão

As ferramentas apresentadas formam o arsenal do desenvolvedor React profissional. React.memo previne renderizações quando props não mudam; useMemo cacheia computações; useCallback estabiliza funções para filhos memoizados. Mas a verdadeira maestria vem do Profiler: meça sempre antes de otimizar. Componentes bem estruturados naturalmente têm boa performance. Otimizações pontuais, guiadas por dados, transformam aplicações lentas em rápidas.

Referências


Artigos relacionados