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.
- Ouça o áudio com a tela desligada
- Ganhe Certificado após a conclusão
- + de 5000 cursos para você explorar!
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,TextePressable. - Passo 3: exponha
onPresspara 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ão | Quando usar | Exemplo |
|---|---|---|
&& | Mostrar algo apenas se condição for verdadeira | {isLoading && <Text>Carregando...</Text>} |
| ternário | Alternar entre dois blocos | {error ? <Text>Erro</Text> : <Text>OK</Text>} |
| early return | Componentes com estados bem definidos | if (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
ListHeaderComponentpara cabeçalhos dentro da lista (busca, filtros). - Use
ItemSeparatorComponentpara separadores consistentes. - Evite colocar
ScrollViewenvolvendoFlatList(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,descriptioneimageUrl. - Passo 2: crie estado para
queryeselected. - Passo 3: filtre a lista com base na busca.
- Passo 4: renderize com
FlatList,keyExtractore 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
ProductDetailque recebeproducte callbacks (onBack,onBuy). - Passo 2: use o padrão de layout: header fixo,
ScrollViewno meio, ações fixas embaixo. - Passo 3: use renderização condicional para lidar com
productnulo.
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
ScrollViewpara 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
ListHeaderComponentdoFlatListe mantenha o header fixo apenas com o título. - Renderização condicional: crie um estado
isLoadinge mostre um placeholder de carregamento antes de exibir a lista. - Fragments: renderize pares de
Text(label/valor) sem criarViewextra, usando fragment comkeyquando estiver em loop.