O que “performance” significa no React Native
Em React Native, performance é principalmente sobre tempo de resposta (toques e rolagem sem travar), tempo de renderização (quantos componentes re-renderizam) e custo de UI (layout, sombras, overdraw e imagens). A maioria dos gargalos em apps reais aparece em três áreas: listas (muitos itens), imagens (download/decodificação/redimensionamento) e re-renderizações (estado mal segmentado, props instáveis, funções recriadas).
Sintomas comuns
- Scroll “engasga” em listas longas.
- Ao digitar em um input, a tela inteira re-renderiza.
- Imagens piscam ao voltar para uma tela ou ao rolar.
- Transições e animações ficam com “stutter” quando há sombras pesadas.
Listas rápidas: FlatList e SectionList na prática
FlatList e SectionList já virtualizam itens (renderizam apenas o que está visível), mas ainda é fácil degradar o desempenho com renderItem instável, chaves erradas e itens pesados.
Checklist de otimização (o que mais impacta)
- keyExtractor correto e estável: use um ID único do item, nunca o índice.
- renderItem estável: evite recriar funções e objetos a cada render.
- Item memoizado: use
React.memopara evitar re-render quando props não mudam. - getItemLayout quando altura é fixa: melhora scroll e
scrollToIndex. - windowSize, initialNumToRender e maxToRenderPerBatch ajustados: equilibram memória e fluidez.
- removeClippedSubviews (Android): pode reduzir custo fora da tela (teste, pois pode causar glitches em alguns layouts).
keyExtractor: por que o índice é ruim
Quando você usa o índice como chave, ao inserir/remover itens no topo, o React “acha” que itens mudaram de identidade e pode reaproveitar views erradas, causando re-renderizações e glitches visuais.
// Ruim (instável quando a lista muda): keyExtractor={(item, index) => String(index)} // Bom: keyExtractor={(item) => item.id}Passo a passo: FlatList com renderItem estável e item memoizado
Objetivo: reduzir re-renderizações e manter scroll suave.
Crie um componente de item isolado e memoize com
React.memo.Continue em nosso aplicativo e ...- Ouça o áudio com a tela desligada
- Ganhe Certificado após a conclusão
- + de 5000 cursos para você explorar!
Baixar o aplicativo
import React, {memo} from 'react'; import {Pressable, Text, View, StyleSheet} from 'react-native'; type RowProps = { item: {id: string; title: string; subtitle?: string}; onPress: (id: string) => void; }; export const Row = memo(function Row({item, onPress}: RowProps) { return ( <Pressable onPress={() => onPress(item.id)} style={styles.row}> <View> <Text style={styles.title}>{item.title}</Text> {!!item.subtitle && <Text style={styles.subtitle}>{item.subtitle}</Text>} </View> </Pressable> ); }); const styles = StyleSheet.create({ row: {padding: 12, backgroundColor: '#fff'}, title: {fontSize: 16, fontWeight: '600'}, subtitle: {fontSize: 13, color: '#666'} });Garanta que o callback
onPressseja estável comuseCallback.import React, {useCallback} from 'react'; import {FlatList} from 'react-native'; import {Row} from './Row'; export function Screen({data, navigation}) { const handlePress = useCallback((id: string) => { navigation.navigate('Details', {id}); }, [navigation]); const renderItem = useCallback(({item}) => { return <Row item={item} onPress={handlePress} />; }, [handlePress]); return ( <FlatList data={data} keyExtractor={(item) => item.id} renderItem={renderItem} /> ); }Evite passar props “novas” sem necessidade (objetos/arrays inline). Se precisar de estilos dinâmicos, prefira
StyleSheet+ condicionais simples, ou memoize o objeto.
getItemLayout (quando itens têm altura fixa)
Se cada item tem altura previsível (ex.: 72px), informe ao FlatList. Isso reduz trabalho de medição e melhora operações como scrollToIndex.
const ITEM_HEIGHT = 72; <FlatList data={data} keyExtractor={(item) => item.id} getItemLayout={(_, index) => ({ length: ITEM_HEIGHT, offset: ITEM_HEIGHT * index, index })} />Ajustes finos de virtualização (use com critério)
Essas props mudam o equilíbrio entre memória e fluidez. Ajuste medindo (perfilando) em dispositivos reais.
<FlatList data={data} keyExtractor={(item) => item.id} renderItem={renderItem} initialNumToRender={10} maxToRenderPerBatch={10} updateCellsBatchingPeriod={50} windowSize={5} removeClippedSubviews={true} />initialNumToRender: quantos itens renderizar na primeira tela.maxToRenderPerBatcheupdateCellsBatchingPeriod: controlam “lotes” de render.windowSize: quantas telas (acima/abaixo) manter montadas.
SectionList: mesmos princípios, mais um cuidado
Em SectionList, além do item, headers de seção também podem re-renderizar muito. Memoize headers e evite criar sections como novo array a cada render (por exemplo, derivando de estado global sem memoização).
const sections = useMemo(() => buildSections(rawData), [rawData]); <SectionList sections={sections} keyExtractor={(item) => item.id} renderItem={renderItem} renderSectionHeader={renderHeader} />Reduzindo re-renderizações: memo, useCallback e segmentação de estado
Entenda o custo: “render em cascata”
Quando um componente pai re-renderiza, seus filhos re-renderizam por padrão. Isso é normal, mas vira gargalo quando o pai muda com frequência (ex.: digitação, timers, listeners) e a árvore é grande (ex.: tela com lista + header + filtros).
Quando usar React.memo
- Componentes de item de lista.
- Componentes de UI reutilizáveis com props simples.
- Partes da tela que não dependem do estado que muda frequentemente.
React.memo funciona melhor quando as props são estáveis. Se você passa objetos/funções recriados a cada render, o memo perde efeito.
useCallback e useMemo: uso objetivo
useCallback: estabiliza referência de funções passadas para filhos memoizados ou para listas.useMemo: estabiliza valores derivados caros (ex.: filtrar/ordenar grande lista) e evita recriar objetos passados como props.
const filtered = useMemo(() => { return data.filter((x) => x.name.includes(query)); }, [data, query]); const onChangeQuery = useCallback((text) => setQuery(text), []);Passo a passo: separar estado “quente” do resto da tela
Objetivo: digitar em um campo sem re-renderizar a lista inteira.
Extraia a lista para um componente memoizado.
const ResultsList = React.memo(function ResultsList({data, onPress}) { const renderItem = React.useCallback(({item}) => ( <Row item={item} onPress={onPress} /> ), [onPress]); return ( <FlatList data={data} keyExtractor={(item) => item.id} renderItem={renderItem} /> ); });Mantenha o estado do input no componente do input (ou em um componente acima, mas sem forçar a lista a re-renderizar sem necessidade).
function SearchScreen({data, navigation}) { const [query, setQuery] = React.useState(''); const onPress = React.useCallback((id) => navigation.navigate('Details', {id}), [navigation]); const filtered = React.useMemo(() => data.filter((x) => x.title.includes(query)), [data, query]); return ( <> <SearchBox value={query} onChangeText={setQuery} /> <ResultsList data={filtered} onPress={onPress} /> </> ); }
Estados globais mal segmentados: o que observar
Um padrão que causa lentidão é ter um estado global grande (ex.: um objeto enorme) e qualquer mudança nele disparar re-render em muitas telas/partes. Sinais:
- Uma pequena alteração (ex.: contador, flag) faz a lista inteira re-renderizar.
- Componentes que não usam aquele pedaço do estado ainda re-renderizam.
Mitigações práticas:
- Selecione apenas o necessário (evite “pegar o store inteiro”).
- Quebre estado em fatias menores (por feature).
- Prefira derivar dados localmente com memoização quando fizer sentido.
Imagens: carregamento, cache, placeholders e dimensionamento
Por que imagens travam o app
Imagens custam em três etapas: rede (download), decodificação (transformar bytes em bitmap) e render (desenhar na tela). Além disso, imagens grandes consomem memória e podem causar quedas de FPS durante scroll.
Dimensionamento correto (regra prática)
- Evite carregar uma imagem 3000x3000 para exibir em 120x120.
- Se a API permitir, peça tamanhos adequados (thumbnails para listas, tamanho maior só no detalhe).
- Defina width/height no componente para evitar relayout e “pulos” de layout.
<Image source={{uri: item.thumbUrl}} style={{width: 64, height: 64, borderRadius: 8}} />Cache de imagens (quando usar biblioteca)
O componente Image pode se beneficiar de cache do sistema, mas em apps com muitas imagens remotas é comum usar uma biblioteca com cache mais previsível e controle de prioridade. Uma opção amplamente usada é react-native-fast-image (quando compatível com sua versão/stack), que oferece cache e melhor controle de carregamento.
// Exemplo conceitual (verifique compatibilidade e instalação no seu projeto) import FastImage from 'react-native-fast-image'; <FastImage style={{width: 64, height: 64}} source={{ uri: item.thumbUrl, priority: FastImage.priority.normal, cache: FastImage.cacheControl.immutable }} resizeMode={FastImage.resizeMode.cover} />Placeholders e transição suave
Para evitar “piscadas” e melhorar percepção de velocidade:
- Mostre um placeholder (cor sólida, skeleton) enquanto carrega.
- Use
onLoadStart/onLoadEndpara controlar estado de carregamento. - Evite re-renderizar o item inteiro quando a imagem termina de carregar; isole a imagem em um componente próprio.
function Avatar({uri}) { const [loading, setLoading] = React.useState(true); return ( <View style={{width: 64, height: 64}}> {loading && <View style={{position: 'absolute', inset: 0, backgroundColor: '#eee', borderRadius: 8}} />} <Image source={{uri}} style={{width: 64, height: 64, borderRadius: 8}} onLoadStart={() => setLoading(true)} onLoadEnd={() => setLoading(false)} /> </View> ); }resizeMode e custo visual
cover: bom para thumbs, mas pode cortar; combine com tamanho correto.contain: evita corte, mas pode deixar “barras”.- Evite redimensionar imagens enormes no cliente; prefira servir no tamanho certo.
Sombras, overdraw e custo de UI
Sombras: bonitas, mas caras
Sombras podem aumentar o custo de renderização, especialmente em listas (muitos itens com sombra). Em iOS, sombras com shadowRadius/shadowOpacity são custosas; em Android, elevation costuma ser mais eficiente, mas ainda pesa em grandes quantidades.
Práticas objetivas:
- Evite sombra em todos os itens de uma lista longa; use bordas, separadores ou sombra apenas em cards principais.
- Prefira sombras leves (raio menor, opacidade menor).
- Evite múltiplas camadas com transparência (aumenta overdraw).
Overdraw: desenhar mais do que o necessário
Overdraw acontece quando a GPU desenha pixels várias vezes (ex.: fundo da tela + fundo do container + fundo do card + overlay). Em listas, isso se multiplica rapidamente.
- Evite backgrounds redundantes (ex.: pai e filho com a mesma cor).
- Reduza overlays sem necessidade (gradientes, opacidades grandes).
- Use
StyleSheete layouts simples para itens de lista.
Perfilamento e diagnóstico: encontrando gargalos reais
O que medir primeiro
- Re-renderizações: quantas vezes um componente renderiza ao interagir.
- Tempo de JS: picos ao rolar lista ou ao digitar.
- Tempo de UI: quedas de FPS por layout pesado (sombras, overdraw).
- Rede e imagens: tempo de carregamento e cache.
Passo a passo: identificar renders em cascata com logs controlados
Adicione logs temporários em componentes suspeitos (principalmente pais de listas).
function BigScreen() { console.log('BigScreen render'); ... }Interaja com a tela (scroll, digitação, abrir modal) e observe se renders disparam em excesso.
Isole: extraia subárvores para componentes memoizados e estabilize props (callbacks/objetos).
React DevTools Profiler (quando disponível)
Use o Profiler para ver quais componentes renderizam mais e quanto tempo levam. Foque em:
- Componentes que renderizam muitas vezes por interação.
- Componentes com render lento (tempo alto).
- Props que mudam sem necessidade (referências novas).
Flipper (quando aplicável) e inspeções úteis
Em projetos onde o Flipper está habilitado, ele ajuda a investigar:
- Network: chamadas repetidas, payloads grandes, falta de cache.
- Layout/Performance plugins (dependendo do setup): sinais de quedas de FPS e jank.
- Logs: correlacionar ações do usuário com picos de trabalho.
Mesmo sem plugins avançados, a combinação de Network + logs + Profiler costuma apontar rapidamente o gargalo principal.
Gargalos frequentes e como reconhecer
| Gargalo | Sintoma | Correção típica |
|---|---|---|
| renderItem recriado | Lista re-renderiza itens sem necessidade | useCallback no renderItem e item com React.memo |
| keyExtractor instável | Itens “trocam” conteúdo, flicker | Use ID único do item |
| Imagens grandes | Scroll trava, memória alta | Servir thumbnail, definir tamanho, cache |
| Sombras em massa | FPS cai em listas | Remover/amenizar sombras, simplificar UI |
| Estado global amplo | Qualquer mudança re-renderiza muita coisa | Segmentar estado, selecionar fatias, memoizar derivados |
| Objetos inline em props | Memo não funciona | useMemo ou constantes fora do render |
Guia rápido: sequência recomendada de otimização
- Meça: identifique se o problema é lista, imagem, JS ou UI.
- Corrija chaves e estabilize
renderItem. - Memoize itens e separe subárvores para reduzir cascata.
- Ajuste virtualização (
getItemLayout,windowSize) com base em testes. - Otimize imagens (tamanho correto, cache, placeholder).
- Revise UI pesada (sombras/overdraw) em telas com scroll.