5 dicas de performance no React Native
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 render
novamente) 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 render
novamente 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 Component
comum 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 🙂