Componentes fundamentais do React Native e composição de UI

Capítulo 3

Tempo estimado de leitura: 14 minutos

+ Exercício

Componentes base: o “vocabulário” da UI

Em React Native, a interface é construída compondo componentes. Os componentes base (core components) são blocos prontos que você combina para formar telas reais. A ideia central é: UI = árvore de componentes, onde cada nó recebe props (configurações) e pode conter children (conteúdo interno).

View

View é o contêiner mais comum. Use para agrupar elementos, aplicar layout (flexbox), espaçamento, bordas e fundo.

<View style={{ padding: 16, backgroundColor: '#fff' }}>  <Text>Conteúdo dentro de uma View</Text></View>

Text

Text renderiza texto. Em React Native, texto deve estar dentro de Text (não pode ser “solto” na tela).

<Text style={{ fontSize: 18, fontWeight: '600' }}>Título</Text><Text style={{ color: '#666' }}>Descrição menor</Text>

Image

Image exibe imagens locais ou remotas. Para imagens remotas, use source={{ uri }} e defina dimensões (ou estilo que determine tamanho).

<Image  source={{ uri: 'https://picsum.photos/200' }}  style={{ width: 80, height: 80, borderRadius: 12 }}  resizeMode="cover"/>

ScrollView

ScrollView é um contêiner rolável que renderiza todos os filhos de uma vez. É ótimo para conteúdo curto/médio (formulários, detalhes), mas não é ideal para listas longas.

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

<ScrollView contentContainerStyle={{ padding: 16 }}>  <Text>Conteúdo longo...</Text></ScrollView>

FlatList

FlatList é a lista virtualizada padrão para grandes coleções. Ela renderiza apenas o necessário e recicla itens conforme você rola.

const data = [  { id: '1', title: 'Item 1' },  { id: '2', title: 'Item 2' },];<FlatList  data={data}  keyExtractor={(item) => item.id}  renderItem={({ item }) => (    <Text style={{ padding: 12 }}>{item.title}</Text>  )}/>

SectionList

SectionList é uma lista com seções (ex.: contatos por letra, produtos por categoria). Cada seção tem um title e um array data.

const sections = [  { title: 'Frutas', data: ['Maçã', 'Banana'] },  { title: 'Legumes', data: ['Cenoura', 'Batata'] },];<SectionList  sections={sections}  keyExtractor={(item, index) => item + index}  renderSectionHeader={({ section }) => (    <Text style={{ padding: 12, fontWeight: '700' }}>{section.title}</Text>  )}  renderItem={({ item }) => (    <Text style={{ paddingHorizontal: 12, paddingVertical: 8 }}>{item}</Text>  )}/>

Pressable e Touchable

Para interações, prefira Pressable (mais moderno e flexível). TouchableOpacity/TouchableHighlight ainda existem e são úteis, mas Pressable permite estilos por estado (pressionado, foco, etc.).

<Pressable  onPress={() => console.log('clicou')}  style={({ pressed }) => ({    padding: 12,    borderRadius: 10,    backgroundColor: pressed ? '#ddd' : '#eee',  })}>  <Text>Abrir</Text></Pressable>

TextInput

TextInput captura entrada do usuário. Em geral, você controla o valor com estado (value + onChangeText).

const [query, setQuery] = useState('');<TextInput  value={query}  onChangeText={setQuery}  placeholder="Buscar..."  style={{    borderWidth: 1,    borderColor: '#ccc',    borderRadius: 10,    paddingHorizontal: 12,    paddingVertical: 10,  }}/>

KeyboardAvoidingView

Quando o teclado aparece, ele pode cobrir campos e botões. KeyboardAvoidingView ajuda a empurrar o conteúdo para cima. Use em telas com formulário e, frequentemente, combine com ScrollView.

<KeyboardAvoidingView style={{ flex: 1 }} behavior="padding">  <ScrollView contentContainerStyle={{ padding: 16 }}>    <TextInput placeholder="Email" />    <TextInput placeholder="Senha" secureTextEntry />  </ScrollView></KeyboardAvoidingView>

Composição: props, children e componentes reutilizáveis

Composição é criar componentes menores e combiná-los para formar componentes maiores. Isso reduz repetição e melhora manutenção.

Props: configurando comportamento e aparência

Props são entradas do componente. Pense nelas como parâmetros.

function Badge({ label, color = '#333' }) {  return (    <View style={{ paddingHorizontal: 10, paddingVertical: 6, borderRadius: 999, backgroundColor: color }}>      <Text style={{ color: '#fff', fontWeight: '600' }}>{label}</Text>    </View>  );}

Children: conteúdo “dentro” do componente

children permite que um componente embrulhe outros.

function Card({ children }) {  return (    <View style={{ backgroundColor: '#fff', borderRadius: 16, padding: 16, shadowOpacity: 0.08, shadowRadius: 10 }}>      {children}    </View>  );}/* uso */<Card>  <Text style={{ fontSize: 18, fontWeight: '700' }}>Resumo</Text>  <Text style={{ marginTop: 8, color: '#666' }}>Conteúdo do card</Text></Card>

Passo a passo: criando um componente de item de lista

Vamos criar um item reutilizável para listas (imagem + título + subtítulo + ação).

  • Passo 1: defina a interface via props (dados e callbacks).
  • Passo 2: componha layout com View, Image, Text e Pressable.
  • Passo 3: exponha onPress para navegação/ação.
function ListItem({ title, subtitle, imageUrl, onPress }) {  return (    <Pressable      onPress={onPress}      style={({ pressed }) => ({        flexDirection: 'row',        gap: 12,        padding: 12,        borderRadius: 14,        backgroundColor: pressed ? '#f0f0f0' : '#fff',        alignItems: 'center',      })}    >      <Image source={{ uri: imageUrl }} style={{ width: 56, height: 56, borderRadius: 12 }} />      <View style={{ flex: 1 }}>        <Text style={{ fontSize: 16, fontWeight: '700' }} numberOfLines={1}>{title}</Text>        <Text style={{ marginTop: 2, color: '#666' }} numberOfLines={2}>{subtitle}</Text>      </View>      <Text style={{ color: '#999' }}>></Text>    </Pressable>  );}

Renderização condicional: exibindo o que faz sentido

Renderização condicional é decidir o que mostrar com base em estado/dados. Use com cuidado para manter a UI previsível.

Padrões comuns

PadrãoQuando usarExemplo
&&Mostrar algo apenas se condição for verdadeira{isLoading && <Text>Carregando...</Text>}
ternárioAlternar entre dois blocos{error ? <Text>Erro</Text> : <Text>OK</Text>}
early returnComponentes com estados bem definidosif (isLoading) return <Loading />;

Exemplo prático: vazio vs lista

function Results({ items }) {  if (!items) return null;  if (items.length === 0) {    return <Text style={{ padding: 16, color: '#666' }}>Nenhum resultado.</Text>;  }  return (    <FlatList      data={items}      keyExtractor={(item) => item.id}      renderItem={({ item }) => <Text style={{ padding: 12 }}>{item.title}</Text>}    />  );}

Listas: keyExtractor, performance e boas práticas

Por que a key é importante

React usa a key para identificar itens e atualizar a UI com eficiência. Em listas, use keyExtractor com um identificador estável (id). Evite usar índice quando a lista pode mudar de ordem/inserir/remover itens.

<FlatList  data={products}  keyExtractor={(item) => item.id}  renderItem={({ item }) => <Text>{item.name}</Text>}/>

Otimizações básicas: initialNumToRender e windowSize

Para listas longas, ajuste parâmetros para equilibrar velocidade inicial e fluidez.

  • initialNumToRender: quantos itens renderizar no primeiro paint. Menor = abre mais rápido, mas pode “carregar” conforme rola.
  • windowSize: quantas telas (viewport) manter renderizadas (acima e abaixo). Maior = rolagem mais suave, mas mais memória.
<FlatList  data={feed}  keyExtractor={(item) => item.id}  initialNumToRender={8}  windowSize={7}  renderItem={({ item }) => (    <ListItem      title={item.title}      subtitle={item.subtitle}      imageUrl={item.imageUrl}      onPress={() => {}}    />  )}/>

Outras práticas úteis (sem exagero)

  • Use ListHeaderComponent para cabeçalhos dentro da lista (busca, filtros).
  • Use ItemSeparatorComponent para separadores consistentes.
  • Evite colocar ScrollView envolvendo FlatList (duas rolagens competindo e perda de virtualização).
<FlatList  data={items}  keyExtractor={(item) => item.id}  ItemSeparatorComponent={() => <View style={{ height: 10 }} />}  ListHeaderComponent={() => (    <View style={{ padding: 16 }}>      <Text style={{ fontSize: 22, fontWeight: '800' }}>Itens</Text>    </View>  )}  renderItem={({ item }) => <ListItem title={item.title} subtitle={item.subtitle} imageUrl={item.imageUrl} onPress={() => {}} />}/>

Fragments: uso correto para não poluir a árvore

Quando você precisa retornar múltiplos elementos sem criar uma View extra, use fragment (<>...</> ou <React.Fragment>). Isso evita wrappers desnecessários que podem atrapalhar layout (especialmente em flex) e acessibilidade.

function Header() {  return (    <>      <Text style={{ fontSize: 22, fontWeight: '800' }}>Título</Text>      <Text style={{ color: '#666' }}>Subtítulo</Text>    </>  );}

Quando estiver mapeando arrays para múltiplos elementos, use <React.Fragment key="..."> para fornecer key no fragment.

{rows.map((row) => (  <React.Fragment key={row.id}>    <Text>{row.left}</Text>    <Text>{row.right}</Text>  </React.Fragment>))}

Padrões de layout: cabeçalho fixo, conteúdo rolável e área de ações

Um padrão comum em apps: topo fixo (título/ações), meio rolável (conteúdo) e rodapé fixo (botões). A base é uma tela com flex: 1, um header, um container rolável e uma barra de ações.

Estrutura base

function ScreenLayout({ title, children, actions }) {  return (    <View style={{ flex: 1, backgroundColor: '#f6f6f6' }}>      <View style={{ paddingTop: 16, paddingHorizontal: 16, paddingBottom: 12, backgroundColor: '#fff' }}>        <Text style={{ fontSize: 20, fontWeight: '800' }}>{title}</Text>      </View>      <ScrollView contentContainerStyle={{ padding: 16, gap: 12 }}>        {children}      </ScrollView>      <View style={{ padding: 16, backgroundColor: '#fff', flexDirection: 'row', gap: 12 }}>        {actions}      </View>    </View>  );}

Botão reutilizável para a área de ações

function PrimaryButton({ label, onPress, disabled }) {  return (    <Pressable      onPress={onPress}      disabled={disabled}      style={({ pressed }) => ({        flex: 1,        paddingVertical: 12,        borderRadius: 12,        alignItems: 'center',        backgroundColor: disabled ? '#bbb' : pressed ? '#2f6fed' : '#3b82f6',      })}    >      <Text style={{ color: '#fff', fontWeight: '800' }}>{label}</Text>    </Pressable>  );}

Exercício guiado 1: Tela real de lista (com busca) usando FlatList

Objetivo: construir uma tela de “Produtos” com cabeçalho, campo de busca e lista virtualizada. Ao tocar em um item, você seleciona o produto (vamos usar isso no exercício 2).

Passo a passo

  • Passo 1: crie dados de exemplo com id, name, description e imageUrl.
  • Passo 2: crie estado para query e selected.
  • Passo 3: filtre a lista com base na busca.
  • Passo 4: renderize com FlatList, keyExtractor e otimizações básicas.
import React, { useMemo, useState } from 'react';import { View, Text, FlatList, TextInput } from 'react-native';const PRODUCTS = [  { id: 'p1', name: 'Café', description: 'Torrado e moído', imageUrl: 'https://picsum.photos/seed/cafe/200' },  { id: 'p2', name: 'Chá', description: 'Ervas selecionadas', imageUrl: 'https://picsum.photos/seed/cha/200' },  { id: 'p3', name: 'Chocolate', description: '70% cacau', imageUrl: 'https://picsum.photos/seed/choco/200' },];function ListItem({ title, subtitle, imageUrl, onPress }) {  return (    <Pressable      onPress={onPress}      style={({ pressed }) => ({        flexDirection: 'row',        gap: 12,        padding: 12,        borderRadius: 14,        backgroundColor: pressed ? '#f0f0f0' : '#fff',        alignItems: 'center',      })}    >      <Image source={{ uri: imageUrl }} style={{ width: 56, height: 56, borderRadius: 12 }} />      <View style={{ flex: 1 }}>        <Text style={{ fontSize: 16, fontWeight: '700' }} numberOfLines={1}>{title}</Text>        <Text style={{ marginTop: 2, color: '#666' }} numberOfLines={2}>{subtitle}</Text>      </View>    </Pressable>  );}export function ProductsListScreen() {  const [query, setQuery] = useState('');  const [selectedId, setSelectedId] = useState(null);  const filtered = useMemo(() => {    const q = query.trim().toLowerCase();    if (!q) return PRODUCTS;    return PRODUCTS.filter((p) =>      (p.name + ' ' + p.description).toLowerCase().includes(q)    );  }, [query]);  return (    <View style={{ flex: 1, backgroundColor: '#f6f6f6' }}>      <View style={{ padding: 16, backgroundColor: '#fff' }}>        <Text style={{ fontSize: 22, fontWeight: '800' }}>Produtos</Text>        <TextInput          value={query}          onChangeText={setQuery}          placeholder="Buscar..."          style={{            marginTop: 12,            borderWidth: 1,            borderColor: '#ddd',            borderRadius: 12,            paddingHorizontal: 12,            paddingVertical: 10,            backgroundColor: '#fff',          }}        />      </View>      {filtered.length === 0 ? (        <Text style={{ padding: 16, color: '#666' }}>Nenhum produto encontrado.</Text>      ) : (        <FlatList          contentContainerStyle={{ padding: 16, gap: 12 }}          data={filtered}          keyExtractor={(item) => item.id}          initialNumToRender={8}          windowSize={7}          renderItem={({ item }) => (            <ListItem              title={item.name}              subtitle={item.description}              imageUrl={item.imageUrl}              onPress={() => setSelectedId(item.id)}            />          )}        />      )}      {selectedId && (        <View style={{ padding: 16, backgroundColor: '#fff' }}>          <Text style={{ fontWeight: '700' }}>Selecionado: {selectedId}</Text>        </View>      )}    </View>  );}

Prática: adicione um destaque visual no item selecionado (por exemplo, borda azul) passando uma prop selected para ListItem e alterando o estilo.

Exercício guiado 2: Tela de detalhe (conteúdo rolável + ações fixas)

Objetivo: ao selecionar um item, exibir uma tela de detalhe com imagem, descrição longa rolável e botões fixos na parte inferior.

Passo a passo

  • Passo 1: crie um componente ProductDetail que recebe product e callbacks (onBack, onBuy).
  • Passo 2: use o padrão de layout: header fixo, ScrollView no meio, ações fixas embaixo.
  • Passo 3: use renderização condicional para lidar com product nulo.
function ProductDetail({ product, onBack, onBuy }) {  if (!product) {    return (      <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>        <Text style={{ color: '#666' }}>Selecione um produto.</Text>      </View>    );  }  return (    <View style={{ flex: 1, backgroundColor: '#f6f6f6' }}>      <View style={{ padding: 16, backgroundColor: '#fff', flexDirection: 'row', alignItems: 'center', gap: 12 }}>        <Pressable onPress={onBack} style={{ padding: 10, borderRadius: 10, backgroundColor: '#eee' }}>          <Text>Voltar</Text>        </Pressable>        <Text style={{ fontSize: 20, fontWeight: '800' }} numberOfLines={1}>{product.name}</Text>      </View>      <ScrollView contentContainerStyle={{ padding: 16, gap: 12 }}>        <Image source={{ uri: product.imageUrl }} style={{ width: '100%', height: 220, borderRadius: 16 }} />        <View style={{ backgroundColor: '#fff', borderRadius: 16, padding: 16 }}>          <Text style={{ fontSize: 18, fontWeight: '800' }}>Descrição</Text>          <Text style={{ marginTop: 8, color: '#444', lineHeight: 20 }}>            {product.description}            {'\n\n'}            Detalhes adicionais para simular um texto longo: ingredientes, origem, recomendações de uso e observações.          </Text>        </View>      </ScrollView>      <View style={{ padding: 16, backgroundColor: '#fff', flexDirection: 'row', gap: 12 }}>        <Pressable onPress={onBack} style={{ flex: 1, paddingVertical: 12, borderRadius: 12, alignItems: 'center', backgroundColor: '#eee' }}>          <Text style={{ fontWeight: '800' }}>Cancelar</Text>        </Pressable>        <Pressable onPress={onBuy} style={{ flex: 1, paddingVertical: 12, borderRadius: 12, alignItems: 'center', backgroundColor: '#3b82f6' }}>          <Text style={{ color: '#fff', fontWeight: '800' }}>Comprar</Text>        </Pressable>      </View>    </View>  );}

Prática: substitua o texto “Detalhes adicionais” por um array de bullets e renderize com map usando fragment com key (quando necessário) para evitar wrappers extras.

Exercício guiado 3: Formulário com teclado (TextInput + KeyboardAvoidingView)

Objetivo: criar uma tela de “Checkout” com campos e botão fixo, evitando que o teclado cubra o botão.

Passo a passo

  • Passo 1: envolva a tela em KeyboardAvoidingView.
  • Passo 2: coloque os campos em um ScrollView para permitir rolagem quando o teclado estiver aberto.
  • Passo 3: mantenha a área de ações fixa no rodapé.
function CheckoutScreen() {  const [name, setName] = useState('');  const [email, setEmail] = useState('');  const [address, setAddress] = useState('');  const canSubmit = name.trim() && email.trim() && address.trim();  return (    <KeyboardAvoidingView style={{ flex: 1 }} behavior="padding">      <View style={{ flex: 1, backgroundColor: '#f6f6f6' }}>        <View style={{ padding: 16, backgroundColor: '#fff' }}>          <Text style={{ fontSize: 22, fontWeight: '800' }}>Checkout</Text>        </View>        <ScrollView contentContainerStyle={{ padding: 16, gap: 12 }}>          <View style={{ backgroundColor: '#fff', borderRadius: 16, padding: 16, gap: 10 }}>            <Text style={{ fontWeight: '700' }}>Nome</Text>            <TextInput value={name} onChangeText={setName} placeholder="Seu nome" style={{ borderWidth: 1, borderColor: '#ddd', borderRadius: 12, padding: 12 }} />            <Text style={{ fontWeight: '700' }}>Email</Text>            <TextInput value={email} onChangeText={setEmail} placeholder="seu@email.com" keyboardType="email-address" autoCapitalize="none" style={{ borderWidth: 1, borderColor: '#ddd', borderRadius: 12, padding: 12 }} />            <Text style={{ fontWeight: '700' }}>Endereço</Text>            <TextInput value={address} onChangeText={setAddress} placeholder="Rua, número, bairro" style={{ borderWidth: 1, borderColor: '#ddd', borderRadius: 12, padding: 12 }} />          </View>        </ScrollView>        <View style={{ padding: 16, backgroundColor: '#fff' }}>          <Pressable            disabled={!canSubmit}            onPress={() => {}}            style={({ pressed }) => ({              paddingVertical: 14,              borderRadius: 14,              alignItems: 'center',              backgroundColor: !canSubmit ? '#bbb' : pressed ? '#2f6fed' : '#3b82f6',            })}          >            <Text style={{ color: '#fff', fontWeight: '800' }}>Finalizar</Text>          </Pressable>        </View>      </View>    </KeyboardAvoidingView>  );}

Desafios rápidos (para fixar)

  • Lista com seções: transforme os produtos em categorias e renderize com SectionList. Dica: cada seção precisa de { title, data }.
  • Header na lista: mova o campo de busca para ListHeaderComponent do FlatList e mantenha o header fixo apenas com o título.
  • Renderização condicional: crie um estado isLoading e mostre um placeholder de carregamento antes de exibir a lista.
  • Fragments: renderize pares de Text (label/valor) sem criar View extra, usando fragment com key quando estiver em loop.

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

Ao exibir uma grande lista de itens em React Native, qual abordagem tende a oferecer melhor desempenho e por quê?

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

Você errou! Tente novamente.

FlatList é indicada para grandes coleções porque faz virtualização: renderiza apenas os itens necessários e recicla componentes conforme você rola, melhorando performance e uso de memória. Já ScrollView renderiza todos os filhos de uma vez.

Próximo capitúlo

Estilização em React Native: StyleSheet, flexbox e design consistente

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

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.