5 dicas de performance no React Native

React 21 de Fev de 2018

Apesar do React Native ser uma biblioteca que nos permite alcançar uma performance muito semelhante à nativa temos que cuidar cuidado com alguns detalhes. Os principais problemas relacionados à performance da aplicação estão ligados com a thread do Javascript rodando no App. Nesse post relacionei 5 dicas para fugir de problemas performáticos em seu app.

1. Controlando ciclo de renderização

Um dos maiores agentes por lentidões no fluxo de renderização dos componentes dentro do React é não monitorar quando o componente realmente deve atualizar ou não.

Provavelmente já é sabido por você que todo componente renderiza novamente (executa seu fluxo de update e consequentemente o rendernovamente) toda vez que alguma propriedade ou estado sejam alterados.

Mas será que precisamos executar o render novamente toda vez que qualquer informação dessas alterar? A resposta é não. Isso porque muitas vezes o nosso método render não depende de uma dessas informações que atualizou e é executado novamente sem necessidade, por exemplo, imaginemos o seguinte componente:

class Example extends Component {
  state = {
    title: 'Meu app',
    description: 'Uma descrição' 
  };

  render() {
    return (
      <Text>{this.state.title}</Text>
    );
  }
}

Agora vamos pensar que em algum momento a informação de descrição contida no estado da aplicação é alterada. O React vai executar o rendernovamente pois uma informação do estado atualizou, mas deveria? O nosso render nem depende da descrição, certo? Bora ver como podemos solucionar isso:

Utilizando shouldComponentUpdate

Durante o fluxo de atualização de um componente no React o método shouldComponentUpdate é invocado para decidir se o componente deve renderizar novamente ou não e podemos fazer uma verificação baseado nas novas propriedades e novo estado recebido. Esse método deve retornar sempre um valor booleano (true/false).

Imagine então aplicando ao caso acima, que só queremos que a atualização ocorra se o título contido no estado alterar:

class Example extends Component {
  ...

  shouldComponentUpdate(nextProps, nextState) {
    return this.state.title !== nextState.title;
  }

  ...
}

Dessa forma, quando o estado description atualizar, o método shouldComponentUpdate irá retornar false e, logo, não irá produzir um novo ciclo de renderização otimizando a performance.

Utilizando PureComponent

Como esse comportamento é muito comum e provavelmente você pensou “Nossa, o React não faz isso sozinho?”. Existe um certo porquê dele não fazer isso sozinho, basicamente fazer a checagem de igualdade entre propriedades e estado a cada ciclo de atualização do componente no React pode acabar sendo mais custoso que simplesmente renderizar novamente independente do render utilizar as novas informações ou não.

Mesmo assim, o React nos deixa disponível uma classe chamada PureComponent que possui funcionamento semelhante ao Componentcomum exceto por ela implementar automaticamente o método shouldComponentUpdate com as verificações simples de igualdade entre propriedades e estado:

import React, { PureComponent } from 'react';

class Example extends PureComponent {
  state = {
    title: 'Meu app',
    description: 'Uma descrição'
  };

  render() {
    return (
      <Text>{this.state.title}</Text>
    );
  }
}

Nesse caso acima, o render só será chamado novamente se o título contido no estado do componente alterar. Vale lembrar que utilizando o PureComponent não deve mais ser possível implementar o método shouldComponentUpdate manualmente.

“Ah, mas então eu devo utilizar o PureComponente sempre né?” Noooopsss. Apesar de fazer a checagem simples entre igualdade de propriedades e estado, o PureComponent é recomendado apenas quando essas informações são simples, objetos, vetores unidimensionais, nada além disso, para estruturas mais complexas o React não irá conseguir fazer a verificação de igualdade performática e nesse caso é melhor definir o método shouldComponentUpdate na mão em um Component.

2. Utilizando NativeDriver para animações

Uma dica rápida porém muito útil quando o assunto é animações é tentar utilizar a propriedade useNativeDriver sempre que possível:

Antes:

Animated.timing(this.state.animatedValue, {
  toValue: 1,
  duration: 500,
}).start();

Depois:

Animated.timing(this.state.animatedValue, {
  toValue: 1,
  duration: 500,
  useNativeDriver: true, // <-- Adicione essa linha
}).start();

Isso porque as animações podem ser controladas pela interface nativa ou pela thread do Javascript rodando sobre nossa aplicação e aplicando a propriedade useNativeDriver estamos deferindo a responsabilidade de animar o componente ao código nativo e dessa forma otimizando a performance da mesma.

“Ah, por que isso não vem por padrão?” Apesar de muito útil, nem todas animações são suportadas pelo driver nativo, podemos apenas animar nativamente propriedades não relacionadas à layout, como opacidade, transform ou margens, mas jamais propriedades relacionadas ao flexbox layout. Outra limitação é que as animações de gestos do usuário controladas pelo PanResponder também não podem ser deferidas ao driver nativo.

3. Navegação nativa

Apesar de mais simples, a navegação controlada pela thread do Javascript como por exemplo o React Navigation pode se comportar de forma menos performática em aplicações que exijam muito das telas abertas. Nesses casos é melhor recorrer à soluções que implementam a navegação no lado nativo do código como o Native Navigation da Wix.

Quando utilizar?

Apesar de mais performática eu ainda acredito que em muitos casos o React Navigation é mais recomendado pela simplicidade de instalação e configuração por isso eu busco utilizar uma biblioteca nativa de gerenciamento de rotas apenas se as telas da minha aplicação exigem muito processamento, como por exemplo, câmera, mapas ou recursos que precisam de atualização constante.

Isso porquê a navegação via JS thread mantém os processos das rotas anteriores rodando em background e praticamente não consegue gerenciar a memória e processador gastos para cada tela dando mais importância à página ativa.

Alguns projetos que recomendo ficar de olho:

Biblioteca de navegação nativa do AirBnb (ainda em beta):

http://airbnb.io/native-navigation/

Versão 2 do Native Navigation da Wix (em desenvolvimento):

https://github.com/wix/react-native-navigation/tree/v2

4. Renderização de listas

Se você começou há pouco tempo com o React Native deve ter percebido que existem muitos componentes para renderizar listas, podemos utilizar o ListView, FlatList, SectionList, VirtualizedList ou ainda utilizar só o map do Javascript dentro de uma ScrollView. Mas qual será o mais recomendado quando o assunto é performance?

Não vou estender a resposta, mas basicamente a forma como a ListView gerencia os elementos da lista sempre foi um problema, principalmente por não controlar a memória do dispositivo de forma performática, a partir da versão 0.43 do React Native temos acesso à FlatList, SectionList e VirtualizedList que suprem todas necessidades para listas.

Basicamente utilize FlatList sempre que precisar renderizar uma lista simples com dados contidos em um vetor. SectionList para listas com valores divididos em sessões (vetor de vetores). VirtualizedList para quando os dados não estão obrigatoriamente expostos em um vetor e você precisa manipulá-los para exibir os dados em tela.

5. Cálculos e operações no render

Outro grande erro de quem está começando no React é realizar operações dentro do método render. Voltando ao primeiro item que falamos sobre ciclo de atualização, toda vez que alteramos um estado ou propriedade nosso componente executa o método render novamente e qualquer tipo de cálculo ou definição dentro do mesmo é executada novamente ocupando recursos de processamento do dispositivo.

Exemplo de erro muito comum

Imaginemos que queremos exibir uma lista de dados e dentro dessa lista há uma data com um certo formato e precisamos alterá-lo para exibir ao usuário final, podemos utilizar o MomentJS pra isso, certo?

render() {
  return (
    { this.state.data.map(item => (
      <Text>{ moment(item.date).format('DD/MM/YYYY') }</Text>
    )) }
  );
}

Perfeito, correto?

Toda vez que o ciclo de atualização do nosso componente acontecer, toda nossa lista será renderizada novamente e o cálculo do formato da data para cada item também, imagine quanto processamento está sendo jogado fora por causa disso.

O que fazer?

Basicamente, assim que os dados chegarem no nosso componente, seja por API no componentDidMount ou no recebimento de novas propriedades pelocomponentWillReceiveProps precisamos manipular os dados para deixar no formato correto que iremos utilizar no render não deixando qualquer tipo de cálculo ou operação para o mesmo.


Concluindo

Ufa… terminamos. Com essas 5 dicas tenho certeza que seu aplicativo vai rodar muito mais rápido e seus usuários estarão mais felizes 🙂

Se você gostou dessas 5 dicas deixe suas…

E não esqueça de deixar um comentário caso esse post tenha ajudado você ou até com sugestões para os próximos assuntos 🙂

Marcadores

Diego Fernandes

Programador full-stack, apaixonado pelas melhores tecnologias de desenvolvimento back-end, front-end e mobile, é co-fundador e CTO na Rocketseat.