Aquarela

Aquarela Analytics branco

Maximizando a Performance em React: Técnicas de Memoização, Code Splitting e Virtualização de Listas

react

Em aplicações web modernas, a performance é um fator crítico que pode impactar diretamente a experiência do usuário e o sucesso de um projeto. Problemas de performance, como tempos de carregamento lentos, renderizações lentas e alto consumo de memória, podem ser causados por uma série de fatores, incluindo a manipulação ineficiente do DOM (estrutura de dados que organiza todos os elementos de uma página), grandes volumes de dados e o carregamento excessivo de recursos desnecessários. Esses problemas não só prejudicam a satisfação do usuário, como também podem levar a altas taxas de rejeição e comprometer a eficácia geral da aplicação.

Otimizar a performance é essencial para garantir que sua aplicação funcione de forma rápida e eficiente, mesmo sob alta demanda. Neste artigo, exploraremos técnicas avançadas para melhorar o desempenho de aplicações React, incluindo memoização; code splitting; e virtualização de listas. Acompanhe-nos para descobrir como aplicar essas estratégias e transformar a performance de sua aplicação web.

Memoização

Em aplicações desenvolvidas com React, qualquer mudança no estado ou em propriedades (props) de um componente pode fazer com que ele e seus componentes “filhos” sejam re-renderizados na tela. Isso pode levar a renderizações desnecessárias, especialmente se os dados não mudaram, impactando negativamente a performance do site quando se ocorre em larga escala.

Para resolver esse problema, existe uma técnica de otimização chamada de Memoização, que armazena (ou “memoriza”) os resultados de funções “caras” ou demoradas para evitar recalculá-los quando os mesmos inputs ocorrem novamente. Em termos simples, é como guardar as respostas de perguntas já feitas para não ter que pensar nelas de novo. No React, a memoização é usada para prevenir renderizações desnecessárias e melhorar a performance da aplicação. As ferramentas do React para aplicação dessa técnica são: React.memo, useMemo e useCallback.

React.memo

O React.memo é um HOC (Higher Order Component) que previne re-renderizações de componentes funcionais. Quando um componente é encapsulado com React.memo, ele só será re-renderizado se suas props mudarem. Isso é especialmente útil para componentes que renderizam frequentemente com as mesmas props.

Utilize React.memo quando você tem componentes funcionais que recebem props estáticas ou quase estáticas. Por exemplo, se você tem um componente de lista que renderiza itens que raramente mudam, o React.memo pode evitar re-renderizações para este caso.

const ListItem = React.memo(({ item }) => {
  return <div>{item}</div>;
});

No exemplo acima, o componente ListItem não será re-renderizado enquanto os itens na lista permanecerem os mesmos.

⚠️ Quando evitar: O React.memo pode introduzir uma pequena sobrecarga devido à verificação de igualdade de props. Por isso, evite utilizá-lo se suas props mudam frequentemente ou se a verificação de igualdade (shallow comparison) é complexa e cara.

useMemo

Por outro lado, o useMemo memoriza valores calculados, ou seja, atua em variáveis, e evita sua a recomputação em renderizações subsequentes. Por exemplo, quando uma operação cara é realizada para calcular um valor, useMemo garante que essa operação só será repetida se uma das dependências do cálculo mudar.

Utilize o useMemo para memorizar valores quando os cálculos para obtê-los são caros. Por exemplo, ao calcular um valor baseado em uma lista grande ou realizar operações matemáticas complexas.

import React, { useState, useMemo } from 'react';

function ExpensiveComponent({ number }) {
  const computeExpensiveValue = (num) => {
    // realizando uma operação cara computacionalmente
    let result = 0;
    for (let i = 0; i < 1000000000; i++) {
      result += num * i;
    }
    return result;
  };

  // utilizando o useMemo
  const expensiveValue = useMemo(() => computeExpensiveValue(number), [number]); // array de dependência com o estado 'number'

  return (
    <div>
      <h2>Valor Caro: {expensiveValue}</h2>
    </div>
  );
}

function App() {
  const [number, setNumber] = useState(1);
  const [counter, setCounter] = useState(0);

  return (
    <div>
      <ExpensiveComponent number={number} />
      <button onClick={() => setCounter(counter + 1)}>Incrementar Contador</button>
      <button onClick={() => setNumber(number + 1)}>Incrementar Número</button>
    </div>
  );
}

export default App;

Neste exemplo, o cálculo do valor caro só será executado novamente se o estado number mudar. Caso contrário, o valor memorizado será reutilizado.

⚠️ Quando evitar: Evite useMemo para cálculos simples ou quando o custo de memorização é maior do que o custo da recomputação.

useCallback

Similar ao useMemo, o useCallback memoriza funções. Isso é útil quando você passa funções como props para componentes filhos, prevenindo a criação de novas instâncias da função em cada renderização, o que poderia causar re-renderizações dos componentes filhos.

Utilize o useCallback para memorizar essas funções e prevenir a criação de novas instâncias da função em cada renderização. Principalmente quando os componentes filhos são otimizados com o React.memo.

import React, { useState, useCallback } from 'react';

function Button({ onClick, children }) {
  return <button onClick={onClick}>{children}</button>;
}

const MemoizedButton = React.memo(Button);

function App() {
  const [count, setCount] = useState(0);
  const [text, setText] = useState('');

  // utilizando o useCallback
  const increment = useCallback(() => {
    setCount((prevCount) => prevCount + 1);
  }, []);

  const decrement = useCallback(() => {
    setCount((prevCount) => prevCount - 1);
  }, []);

  return (
    <div>
      <input
        type="text"
        value={text}
        onChange={(e) => setText(e.target.value)}
      />
      <MemoizedButton onClick={increment}>Incrementar</MemoizedButton>
      <MemoizedButton onClick={decrement}>Decrementar</MemoizedButton>
      <div>Contador: {count}</div>
    </div>
  );
}

export default App;

No exemplo acima, as funções increment” e decrement” são memorizadas com useCallback, o que evita que novas referências dessas funções sejam criadas em cada renderização, a menos que suas dependências (nenhuma, neste caso) mudem. Isso evitará renderizações desnecessárias em componentes que dependem dessas funções, como os botões memorizados com React.memo.

⚠️ Quando evitar: Evite useCallback para funções simples que não causam re-renderizações significativas nos componentes filhos. Pode adicionar complexidade sem benefícios perceptíveis em casos triviais.

Portanto, ao utilizar o React.memo, useMemo e useCallback em sua aplicação web, você pode garantir que ela seja mais eficiente, especialmente quando lida com operações caras ou dados que não mudam frequentemente. Aplicar essas técnicas de forma adequada pode fazer uma grande diferença na experiência do usuário, tornando a aplicação mais rápida. 

Code Splitting

Aplicações grandes podem ter tempos de carregamento inicial lentos porque todo o código da aplicação é carregado de uma vez. Além disso, dispositivos com recursos limitados, como smartphones mais antigos, podem ter dificuldades para carregar e executar grandes pacotes de JavaScript. Isso pode resultar em uma má experiência para o usuário, especialmente em conexões lentas. Para resolver esse problema utiliza-se a técnica de “Code splitting”.  

O Code splitting é uma técnica para dividir o código de uma aplicação em pacotes menores que são carregados sob demanda. Em vez de carregar todo o código da aplicação de uma só vez, é carregado apenas o código necessário para a parte da aplicação que o usuário está acessando no momento. Isso melhora significativamente o tempo de carregamento inicial da aplicação, proporcionando uma experiência de usuário mais rápida. No React, podemos aplicar o code splitting utilizando as seguintes ferramentas: React.lazy e o Suspense; e importações dinâmicas.

React.lazy e Suspense

O React.lazy permite carregar componentes de forma assíncrona. Combinado com o componente Suspense, que mostra um fallback enquanto o componente está sendo carregado, podemos melhorar a performance ao dividir o código.

Use React.lazy e Suspense para carregar componentes de forma assíncrona, especialmente para rotas ou componentes grandes que não são necessários imediatamente.

const OtherComponent = React.lazy(() => import('./OtherComponent'));

const MyComponent = () => (
  <Suspense fallback={<div>Loading...</div>}>
    <OtherComponent />
  </Suspense>
);

No exemplo acima, o React.Lazy  reduzirá o tempo de carregamento inicial ao carregar o componente OtherComponent apenas quando ele for necessário. Isso é útil, por exemplo, em uma aplicação de roteamento onde diferentes páginas da aplicação são carregadas de forma assíncrona. 

Importações Dinâmicas

Importações dinâmicas permitem dividir o código em pacotes menores que são carregados sob demanda. Isso é particularmente útil para carregar partes da aplicação que não são necessárias imediatamente, como rotas secundárias ou componentes pesados.

Utilize importações dinâmicas para dividir o código em pacotes menores que são carregados sob demanda. Ideal para funcionalidades opcionais ou secundárias.

const loadComponent = async () => {
  const { default: Component } = await import('./MyComponent');
  return <Component />;
};

⚠️ Quando evitar: Tanto o React.lazy e Suspense, quanto às importações dinâmicas, devem ser evitadas para componentes e funcionalidades pequenas ou críticas para a experiência inicial do usuário, pois introduz um atraso adicional enquanto estão sendo carregadas.

Code splitting é uma técnica poderosa para melhorar a performance das aplicações React, especialmente à medida que elas crescem em complexidade e tamanho. Ao dividir o código em pacotes menores e carregá-los sob demanda, podemos reduzir significativamente o tempo de carregamento inicial, melhorar o desempenho em dispositivos com recursos limitados e tornar as atualizações mais eficientes. 

Virtualização de Lista

Quando trabalhamos com grandes listas de dados, renderizar todos os itens de uma vez pode ser extremamente ineficiente e lento. Isso se dá porque manter muitos elementos  simultaneamente pode consumir muita memória, tornando o scrolling da lista lento e desajeitado, afetando a experiência do usuário e impactando negativamente a performance da aplicação.

A Virtualização de Lista resolve esse problema renderizando apenas os itens visíveis, ao invés de renderizar todos os elementos de uma vez. Em React, bibliotecas como react-window e react-virtualized facilitam a implementação dessa técnica.

react-window e react-virtualized

Ambas as bibliotecas fornecem componentes que apenas renderizam os itens visíveis de uma lista grande, melhorando significativamente a performance. Elas criam uma janela virtual ao redor dos itens visíveis, permitindo uma experiência de rolagem suave e eficiente.

Use essas bibliotecas quando você precisar renderizar listas grandes (centenas ou milhares de itens) e deseja manter uma performance suave.

import { FixedSizeList as List } from 'react-window';

const Row = ({ index, style }) => (
  <div style={style}>Row {index}</div>
);

const MyList = () => (
  <List
    height={150}
    itemCount={1000}
    itemSize={35}
    width={300}
  >
    {Row}
  </List>
);

⚠️ Quando evitar: Evite para listas pequenas ou quando todos os itens devem ser renderizados por alguma razão específica (ex.: SEO). Essas ferramentas podem introduzir complexidade adicional no código e requer aprendizado sobre a API da biblioteca.

Virtualização de lista é uma técnica essencial para melhorar a performance de aplicações React que lidam com listas grandes. Ao renderizar apenas os itens visíveis, a virtualização resolve problemas comuns como renderização lenta, uso excessivo de memória e desempenho ruim em scrolling. Portanto, aplicar virtualização de lista de forma eficaz pode fazer uma grande diferença em projetos que envolvem grandes volumes de dados ou interfaces complexas.

Conclusão – Maximizando a Performance em React

Em resumo, otimizar a performance em aplicações React é crucial para garantir uma experiência de usuário fluida e eficiente. Memoização, através de React.memo, useMemo e useCallback, previne re-renderizações desnecessárias e reduz cálculos repetitivos. Code splitting, utilizando React.lazy, Suspense e importações dinâmicas, divide o código em pacotes menores, melhorando o tempo de carregamento inicial e o desempenho em dispositivos com recursos limitados. Por fim, a virtualização de listas, implementada com ferramentas como react-window e react-virtualized, permite renderizar apenas os itens visíveis, economizando memória e assegurando um scrolling suave. 

Combinando essas técnicas, podemos construir aplicações React robustas e de boa performance, capazes de oferecer uma experiência de usuário superior, independentemente da complexidade ou do tamanho da aplicação.

Quem é a Aquarela Analytics?

A Aquarela Analytics é vencedora do Prêmio CNI de Inovação e referência nacional na aplicação de Inteligência Artificial corporativa na indústria e em grandes empresas. Por meio da plataforma Vorteris, da metodologia DCM e o Canvas Analítico (Download e-book gratuito), atende clientes importantes, como: Embraer (aeroespacial), Scania, Mercedes-Benz, Grupo Randon (automotivo), SolarBR Coca-Cola (varejo alimentício), Hospital das Clínicas (saúde), NTS-Brasil (óleo e gás), Auren, SPIC Brasil (energia), Telefônica Vivo (telecomunicações), dentre outros.

Acompanhe os novos conteúdos da Aquarela Analytics no Linkedin e assinando a nossa Newsletter mensal!

Deixe um comentário

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *

Send this to a friend