Otimizando performance

Internamente, o React usa diversas técnicas inteligentes para minimizar o número de operações custosas de DOM que são necessárias para alterar a UI. Para muitas aplicações, utilizar React fará com que elas tenham uma rápida interface sem fazer muito esforço para otimizar performance. No entanto, existem diversas maneiras para acelerar sua aplicação React.

Use a build de produção

Se você está fazendo benchmarking ou tendo problemas de performance em suas aplicações React, tenha certeza que você está testando com a build de produção.

Por padrão, o React inclui diversos avisos úteis. Esses avisos são muito úteis em desenvolvimento. Contudo, eles tornam o React maior e mais lento, então você precisa ter certeza que está usando a versão de produção quando faz a publicação de seu app.

Se você não tem certeza se seu processo de build está configurado corretamente, você pode verificar instalando a extensão React Developer Tools para o Chrome. Se você visitar um site que usa React em produção, o ícone terá uma cor de fundo escura:

React DevTools em um site com a versão de produção do React

Se você visitar um site com React em modo de desenvolvimento, o ícone terá um fundo vermelho:

React DevTools em um site com a versão de desenvolvimento do React

É esperado que você use o modo de desenvolvimento enquanto trabalha em seu app, e o modo de produção quando publicar ele para os usuários.

Você encontrar instruções para construir seu app para produção abaixo.

Criando um app React (Create React App)

Se seu projeto é construído com Create React App, execute:

npm run build

Isto irá criar uma build de produção para seu app na pasta build/ de seu projeto.

Lembre que isto é somente necessário antes de publicar para produção. Para desenvolvimento normal, use npm start.

Builds de único arquivo

Nós oferecemos versões de produção prontas do React e React DOM com arquivos únicos:

<script src="https://unpkg.com/react@16/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js"></script>

Lembre que somente arquivos React terminados com .production.min.js são adequados para produção.

Brunch

Para uma build de produção do Brunch mais eficiente, instale o uglify-js-brunch plugin:

# Se você usa npm
npm install --save-dev uglify-js-brunch

# Se você usa Yarn
yarn add --dev uglify-js-brunch

Então, para criar uma build de produção, adicione o argumento -p no comando build:

brunch build -p

Lembre que você somente precisa fazer isso para builds de produção. Você não deve passar o argumento -p ou aplicar esse plugin em desenvolvimento, porque ele irá esconder avisos úteis do React e fará as builds mais lentas.

Browserify

Para uma build de produção do Browserify mais eficiente, instale alguns plugins:

# Se você usa npm
npm install --save-dev envify uglify-js uglifyify 

# Se você usa Yarn
yarn add --dev envify uglify-js uglifyify 

Para criar uma build de produção, tenha certeza que você adicionou esses transforms (a ordem faz diferença):

  • O envify assegura que o ambiente que a build está configurado é o correto. Torne ele global (-g).
  • O uglifyify remove os imports de desenvolvimento. Torna ele global também (-g).
  • Finalmente, o bundle gerado é enviado para o uglify-js para enxutar (entenda o porquê).

Por exemplo:

browserify ./index.js \
  -g [ envify --NODE_ENV production ] \
  -g uglifyify \
  | uglifyjs --compress --mangle > ./bundle.js

Observação:

O nome do pacote é uglify-js, mas o binário gerado é chamado uglifyjs.
Isto não é um erro de digitação.

Lembre que você somente precisar fazer isso para builds de produção. Você não deve aplicar esses plugins em desenvolvimento porque eles vão esconder avisos úteis do React, e farão as builds mais lentas.

Rollup

Para uma build de produção do Rollup mais eficiente, instale alguns plugins:

# Se você usa npm
npm install --save-dev rollup-plugin-commonjs rollup-plugin-replace rollup-plugin-uglify 

# Se você usa Yarn
yarn add --dev rollup-plugin-commonjs rollup-plugin-replace rollup-plugin-uglify 

Para criar uma build de produção, tenha certeza que você adicionou esses plugins, (a ordem faz diferença)

  • O replace assegura que o ambiente em que a build está configurado é o correto.
  • O commonjs fornece suporte para CommonJS no Rollup.
  • O uglify comprime e enxuta o bundle final.
plugins: [
  // ...
  require('rollup-plugin-replace')({
    'process.env.NODE_ENV': JSON.stringify('production')
  }),
  require('rollup-plugin-commonjs')(),
  require('rollup-plugin-uglify')(),
  // ...
]

Para um exemplo completo de setup veja esse gist.

Lembre que você somente precisa fazer isso para builds de produção. Você não deve aplicar o uglify ou o replace com o valor de 'production'em desenvolvimento porque eles vão esconder avisos úteis do React, e farão as builds mais lentas.

webpack

Observação:

Se você está usando Create React App, por favor siga as instruções abaixo.
Esta seção é somente relevante se você configura o webpack diretamente.

Para uma build de produção mais eficiente do webpack, tenha certeza que você incluiu esses plugins em sua configuração de produção:

new webpack.DefinePlugin({
  'process.env.NODE_ENV': JSON.stringify('production')
}),
new webpack.optimize.UglifyJsPlugin()

Você pode aprender mais sobre isso na documentação do webpack.

Lembre que você somente precisa fazer isso para builds de produção. Você não deve aplicar UglifyJsPlugin ou DefinePlugin com o valor de 'production' em desenvolvimento porque eles vão esconder avisos úteis do React, e farão as builds mais lentas.

Analisando componentes com o Chrome Performance Tab

Em modo de desenvolvimento, você pode visualizar como os componentes são montados (mount), alterados (update), and desmontados (unmount), usando as ferramentas de performance nos browsers suportados. Por exemplo:

Componentes do React na linha do tempo do Chrome

Para fazer isso no Chrome:

  1. Temporariamente desabilite todas as extensões do Chrome, especialmente React DevTools. Elas podem significativamente enviesar os resultados!

  2. Tenha certeza que você está rodando sua aplicação no modo de desenvolvimento.

  3. Abra o Chrome DevTools a aba Performance a pressione Record.

  4. Faça as ações que você quer analisar. Não grave mais que 20 segundos ou o Chrome pode travar.

  5. Pare de gravar.

  6. Eventos do React serão agrupados sob a label User Timing.

Para mais detalhes do passo a passo, veja esse artigo do Ben Schwarz.

Perceba que os números são relativos para que os componentes renderizem mais rápido em produção. Ainda, isto deve ajudar você a perceber quando algo não relacionados da UI são alteradas, a quão profundo e frequente suas alterações de UI acontecem.

Atualmente Chrome, Edge e IE são os únicos browsers que suportam essa feature, mas nós usamos um padrão User Timing API então esperamos que mais navegadores deem suporte.

Analisando componentes com o DevTools Profiler

react-dom 16.5+ e react-native 0.57+ fornecem melhorias nas capacidades de analise em modo de desenvolvimento com o React DevTools Profiler.

Uma visão geral do Profiler pode ser encontrada nesse artigo “Introducing the React Profiler”. Um vídeo com o passo a passo do profiler também está disponível no YouTube.

Se você ainda não tem o React DevTools instalado, você pode encontrá-lo aqui:

Observação

Uma analise de uma build de produção do react-dom está disponível como react-dom/profiling. Leia mais sobre como usar esse pacote no fb.me/react-profiling

Virtualizando Longas Listas

Se sua aplicação renderiza longas listas de informação (milhares ou centenas de linhas), nós recomendamos usar uma técnica conhecida como “windowing”. Esta técnica somente renderiza um pequeno conjunto de suas linhas e pode reduzir drasticamente o tempo que ele leva para re-renderizar os componentes bem como o número de nós criados no DOM.

react-window e react-virtualized são as bibliotecas de windowing mais populares. Eles fornecem diversos componentes reutilizáveis para exibir listas, grids e informações tabulares. Você pode também pode criar seu próprio componente de windowing, como o Twitter fez, se você quer algo mais específico para sua aplicacão.

Evite recompilação

O React cria e mantém sua representação interna da UI renderizada. Ele inclui os elementos do React que você retorna dos seus componentes. Essa representação evita que o React crie nós no DOM e acesse os existes sem necessidade, além do que essas operações podem ser mais lentas do que operações em objetos JavaScript. Algumas vezes esse processo é referenciado como “virtual DOM”, mas ele funciona da mesma forma no React Native.

Quando uma propriedade ou estado de um componente é alterado, o React decide se uma atualização do DOM atual é necessária comparando o novo elemento retornado com o antigo. Quando eles não forem iguais, o React irá alterar o DOM.

Você pode agora visualizar essas re-renderizações do virtual DOM como o React DevTools:

No console de desenvolvedor selecione a opção Highlight Updates na aba de React:

Como habilitar os destaques de alteração (highlight updates)

Interaja com sua página e você deve ver as bordas coloridas aparecendo ao redor de qualquer componente que foi re-renderizado. Isto faz com que você perceba re-renders que não são necessários. Você pode aprender mais sobre essa funcionalidade do React DevTools nesse post do Ben Edelstein.

Considere esse exemplo:

Exemplo dos destaques de alterações do React DevTools

Perceba que quando nós estamos acessando o segundo todo, o primeiro todo também pisca na tela a cada tecla digitada. Isto significa que ele está sendo re-renderizando pelo React junto com o input. Isso algumas vez é chamado render desperdiçado (wasted render). Nós sabemos que ele é desnecessário porque o conteúdo do primeiro todo não tem nenhuma mudança, mas o React não sabe sobre isso.

Embora o React somente altere os nós de DOM alterados, o re-rendering ainda leva algum tempo. Em muitos casos isso não é um problema, mas se a lentidão é perceptível, você pode aumentar velocidade dele sobrescrevendo a função de lifecycle shouldComponentUpdate, na qual é chamada antes do processo de re-rendering começar. A implementação padrão dessa função retorna true, deixando o React performar a alteração:

shouldComponentUpdate(nextProps, nextState) {
  return true;
}

Se você sabe que em algumas situações seu componente não precisa ser alterado, você pode retornar false no shouldComponentUpdate ao invés, para pular o todo o processo de renderização, incluindo a chamada de render() nesse componente e seus filhos:

Na maioria dos casos, ao invés de escrever shouldComponentUpdate() na mão, você pode herdar do React.PureComponent. Ele equivale a implementação do shouldComponentUpdate() com uma comparação rasa entre as anteriores e novas propriedades e estados

shouldComponentUpdate em Ação

Aqui é uma sub-árvore de componentes. Para cada uma, SCU define o que o shouldComponentUpdate retorna, e vDOMEq indica se os elementos renderizados pelo React são equivalentes. Finalmente, o círculo de cores indica se o componente tinha de ser reconciliado ou não.

should component update

Já que shouldComponentUpdate retornou false na sub-árvore iniciada no C2, React não tentou renderizar C2, e por consequência não invocou shouldComponentUpdate no C4 e C5.

Para C1 e C3, shouldComponentUpdate retornou true, então o React teve que descer até as folhas para checá-los. Para o C6 shouldComponentUpdate retornou true, e já que os elementos renderizados não são iguais, o React teve que alterar o DOM.

O último caso interessante é o C8. React teve que renderizar este componente, mas já que os elementos que ele retornou eram iguais aos previamente renderizados, ele não teve que alterar o DOM.

Note que o React somente teve de fazer mutações no DOM para o C6, no qual era inevitável. Para C8, ele abortou comparando os elementos React renderizados, e para a sub-árvore do C2 e C7, ele nem mesmo teve que comparar os elementos pois abortou no shouldComponentUpdate, e render não foi chamado.

Exemplos

Se seu componente muda quando as variáveis props.color ou state.count mudam, você poderia ter um shouldComponentUpdate que checa isso:

class CounterButton extends React.Component {
  constructor(props) {
    super(props);
    this.state = {count: 1};
  }

  shouldComponentUpdate(nextProps, nextState) {
    if (this.props.color !== nextProps.color) {
      return true;
    }
    if (this.state.count !== nextState.count) {
      return true;
    }
    return false;
  }

  render() {
    return (
      <button
        color={this.props.color}
        onClick={() => this.setState(state => ({count: state.count + 1}))}>
        Count: {this.state.count}
      </button>
    );
  }
}

Nesse código, shouldComponentUpdate só está checando se houve alguma mudança no props.color ou state.count. Se esses valores não são alterados, o componente não é alterado. Se seu componente ficou mais complexo, você pode usar um padrão similar fazendo uma comparação rasa (shallow comparison) entre todos os fields de props e state para determinar se o componente deve ser atualizado. Esse padrão é comum o suficiente para que o React forneça um helper para usar essa lógica - apenas herde de React.PureComponent. Então, essa é uma maneira mais simples de alcançar a mesma coisa:

class CounterButton extends React.PureComponent {
  constructor(props) {
    super(props);
    this.state = {count: 1};
  }

  render() {
    return (
      <button
        color={this.props.color}
        onClick={() => this.setState(state => ({count: state.count + 1}))}>
        Count: {this.state.count}
      </button>
    );
  }
}

Na maior parte das vezes, você pode usar React.PureComponent em vez de escrever seu próprio shouldComponentUpdate. Ele somente faz comparações rasas, então você não pode usá-lo caso as props ou state tenham sido alteradas de uma maneira que a comparação rasa não iria detectar.

Isso pode ser um problema com estruturas mais complexas. Por exemplo, vamos dizer que você quer um componente ListOfWords para renderizar uma lista de palavras separas por vírgulas, com um componente pai WordAdder que deixa você clicar em um botão para adicionar uma palavra para a lista. Esse código não faz o trabalho corretamente:

class ListOfWords extends React.PureComponent {
  render() {
    return <div>{this.props.words.join(',')}</div>;
  }
}

class WordAdder extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      words: ['marklar']
    };
    this.handleClick = this.handleClick.bind(this);
  }

  handleClick() {
    // Essa parte é um padrão ruim e causa um bug
    const words = this.state.words;
    words.push('marklar');
    this.setState({words: words});
  }

  render() {
    return (
      <div>
        <button onClick={this.handleClick} />
        <ListOfWords words={this.state.words} />
      </div>
    );
  }
}

O problema é que PureComponent vai fazer um comparação simples entre o valores antigos e novos de this.props.words. Já que esse código muta a lista de words no método handleClick do WordAdder, os antigos e novos valores de this.props.words serão comparados como iguais, mesmo que as atuais palavras da lista tenham mudado. A ListOfWords não irá alterar ainda que haja novas palavras que deveriam ser renderizadas.

O Poder de Não Mutar Dados

A maneira mais simples desse problema não acontecer é evitar mutar valores que são usados como propriedades ou estado. Por exemplo, o método handleClick abaixo poderia ser reescrito usando concat como:

handleClick() {
  this.setState(state => ({
    words: state.words.concat(['marklar'])
  }));
}

ES6 suporta a sintaxe de espalhamento no qual pode fazer isso mais fácil. Se você está usando Creact React App, esta sintaxe é disponível por padrão.

handleClick() {
  this.setState(state => ({
    words: [...state.words, 'marklar'],
  }));
};

Você pode também reescrever o código que muta os objetos para evitar mutação, em uma maneira similar. Por exemplo, vamos dizer que nós temos um objeto chamado colormap e nós queremos escrever uma função que muda colormap.right para 'blue'. Você poderia escrever:

function updateColorMap(colormap) {
  colormap.right = 'blue';
}

Para escrever isso sem mutar o objeto original, nós poderíamos usar o método Object.assign:

function updateColorMap(colormap) {
  return Object.assign({}, colormap, {right: 'blue'});
}

updateColorMap agora retorna um novo objeto, ao invés de mutar o valor o antigo. Object.assign é ES6 e requer um polyfill.

Há uma proposta JavaScript para adicionar espalhador de propriedades de objeto para fazer ele alterar sem mutar.

function updateColorMap(colormap) {
  return {...colormap, right: 'blue'};
}

Se você está usando Create React App, ambos Object.assign e a sintaxe de espalhador de objeto estão disponíveis por padrão.

Usando Estruturas De Dados Mutáveis

Immutable.js é uma outra maneira de resolver esse problema. Ele fornece imutabilidade, persistente coleções que trabalham via compartilhamento estrutural:

  • Imutabilidade: uma vez criado, uma coleção não pode ser mais alterada.
  • Persistência: novas coleções podem ser criadas de coleções antigas e uma mutação como um conjunto. A coleção original ainda é válida depois que a nova coleção é criada.
  • Compartilhamento estrutural: novas coleções são criadas usando o máximo possível de mesma estrutura original, reduzindo cópia para o mínimo para melhorar a performance.

Imutabilidade faz rastrear mudanças de forma barata. Uma mudança irá sempre resultar em um novo objeto onde nós somente precisaremos checar se a referência para o objeto mudou. Por exemplo, nesse exemplo de código JavaScript:

const x = { foo: 'bar' };
const y = x;
y.foo = 'baz';
x === y; // true

Embora y foi editado, desde que sua referência para o objeto x, essa comparação retorna true. Você pode escrever um código similar com immutable.js:

const SomeRecord = Immutable.Record({ foo: null });
const x = new SomeRecord({ foo: 'bar' });
const y = x.set('foo', 'baz');
const z = x.set('foo', 'bar');
x === y; // false
x === z; // true

Nesse caso, já que uma nova referência é retornada quando mutamos x, nós podemos usar a referência para checar a equalidade (x === y) para verificar que o novo valor armazenado em y é diferente que o valor original em x.

Outras bibliotecas que podem ajudar a usar dados imutáveis são Immer, immutability-helper, e seamless-immutable.

Estruturas de dados imutáveis fornecem para você uma maneira barata para rastrear mudanças em objetos, no qual é tudo que nós precisamos para implementar shouldComponentUpdate. Isso pode oferecer a você um bom impulsionamento de performance.