Performance em React Native: listas, imagens, memoização e perfilamento

Capítulo 13

Tempo estimado de leitura: 10 minutos

+ Exercício

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.memo para 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.

  1. 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!
    ou continue lendo abaixo...
    Download App

    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'} });
  2. Garanta que o callback onPress seja estável com useCallback.

    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} /> ); }
  3. 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.
  • maxToRenderPerBatch e updateCellsBatchingPeriod: 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.

  1. 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} /> ); });
  2. 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/onLoadEnd para 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 StyleSheet e 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

  1. Adicione logs temporários em componentes suspeitos (principalmente pais de listas).

    function BigScreen() { console.log('BigScreen render'); ... }
  2. Interaja com a tela (scroll, digitação, abrir modal) e observe se renders disparam em excesso.

  3. 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

GargaloSintomaCorreção típica
renderItem recriadoLista re-renderiza itens sem necessidadeuseCallback no renderItem e item com React.memo
keyExtractor instávelItens “trocam” conteúdo, flickerUse ID único do item
Imagens grandesScroll trava, memória altaServir thumbnail, definir tamanho, cache
Sombras em massaFPS cai em listasRemover/amenizar sombras, simplificar UI
Estado global amploQualquer mudança re-renderiza muita coisaSegmentar estado, selecionar fatias, memoizar derivados
Objetos inline em propsMemo não funcionauseMemo ou constantes fora do render

Guia rápido: sequência recomendada de otimização

  1. Meça: identifique se o problema é lista, imagem, JS ou UI.
  2. Corrija chaves e estabilize renderItem.
  3. Memoize itens e separe subárvores para reduzir cascata.
  4. Ajuste virtualização (getItemLayout, windowSize) com base em testes.
  5. Otimize imagens (tamanho correto, cache, placeholder).
  6. Revise UI pesada (sombras/overdraw) em telas com scroll.

Agora responda o exercício sobre o conteúdo:

Em uma FlatList com muitos itens, qual abordagem tende a reduzir re-renderizações desnecessárias e melhorar a fluidez do scroll?

Você acertou! Parabéns, agora siga para a próxima página

Você errou! Tente novamente.

Chaves estáveis evitam troca de identidade dos itens. renderItem e callbacks estáveis reduzem renders em cascata, e React.memo impede re-render do item quando as props não mudam, melhorando o scroll.

Próximo capitúlo

Qualidade e manutenção em React Native: lint, formatação, testes e acessibilidade

Arrow Right Icon
Capa do Ebook gratuito React Native Essencial: criando apps completos com JavaScript e boas práticas
81%

React Native Essencial: criando apps completos com JavaScript e boas práticas

Novo curso

16 páginas

Baixe o app para ganhar Certificação grátis e ouvir os cursos em background, mesmo com a tela desligada.