Guia completo
React Native: guia de desenvolvimento mobile com Expo
Use este mapa para estudar componentes, estilos, estado, navegação, dados, recursos nativos, testes e publicação com React Native e Expo.
React Native permite usar React para construir aplicativos Android e iOS. Você
escreve componentes com JavaScript ou TypeScript, mas a tela não vira HTML: um
View se transforma em uma estrutura nativa, um Text desenha texto nativo e
um Pressable conversa com o sistema de toque da plataforma.
Neste guia, a gente vai desenhar um aplicativo de hábitos chamado Passo a Passo. Ele cadastra hábitos, marca o que foi feito hoje, persiste dados, navega entre telas e chega ao ponto de gerar uma versão instalável. O código aparece em degraus para você entender o mecanismo antes de depender de bibliotecas.
O pré-requisito real é React: componente, prop, estado, lista e efeito. Se esses nomes ainda se misturam, comece por o que é React e pela lição de useState. React Native muda os blocos da interface e o ambiente; o modelo mental de React continua.
Uma equipe de tradução entre seu componente e o celular
Pense numa produção de cinema internacional. O roteiro descreve a cena, mas cada país monta cenário, iluminação e sinais de acordo com sua realidade. Seu componente React é o roteiro. O renderizador do React Native traduz a intenção para as peças do Android e do iOS.
Na web, React costuma produzir elementos como div, button e input. No
React Native você usa componentes como View, Text, Pressable, TextInput,
Image, ScrollView e FlatList. Eles são os core components, os blocos
centrais oferecidos pelo framework.
A analogia tem limite: não existe um tradutor humano convertendo cada linha em tempo real. A arquitetura do React Native coordena JavaScript e capacidades nativas por interfaces próprias, e o renderizador agenda atualizações. Algumas operações rodam no lado JavaScript; outras, como desenho, gesto e módulos do sistema, envolvem código nativo.
Isso explica duas regras práticas. Primeiro, não copie CSS e HTML esperando que funcionem. Segundo, não execute trabalho pesado no fluxo que precisa responder ao toque. Aplicativo é conversa em tempo real com o usuário; travar a thread JavaScript vira botão que parece quebrado.
React Native, Expo e o fluxo sem framework
React Native é a tecnologia que renderiza e conecta seu código às plataformas. Expo é um framework e conjunto de serviços em volta dela: criação de projeto, bibliotecas nativas, roteamento, atualização, desenvolvimento e builds.
Imagine React Native como motor e estrutura de um carro. Expo oferece painel, chaves, instrumentos e oficina padronizada. Você ainda pode montar o carro diretamente, mas passa a cuidar de mais peças.
A documentação atual do React Native recomenda usar um framework em projetos novos. Para quem está começando, Expo reduz a distância entre criar um componente e vê-lo num aparelho. O fluxo sem framework é útil quando uma base nativa já existe, uma integração exige mudanças profundas em Android/iOS ou a equipe quer controlar essas pastas diretamente.
Usar Expo não significa ficar preso para sempre. Development builds aceitam módulos nativos compatíveis, e o comando de prebuild pode gerar projetos Android e iOS quando necessário. A decisão não é “profissional ou amador”; é quanto de infraestrutura nativa o seu problema realmente exige.
Preparando o ambiente e criando o projeto
Para o caminho com Expo, instale uma versão LTS atual do Node e crie o projeto:
npx create-expo-app@latest passo-a-passo
cd passo-a-passo
npm startO terminal mostra opções para abrir Android, iOS ou web, além de um QR code para um dispositivo compatível com o fluxo de desenvolvimento. A disponibilidade depende do template e das bibliotecas usadas. Quando o projeto precisa de código nativo que não existe no aplicativo genérico, crie um development build.
Antes de escrever funcionalidade, rode quatro verificações:
node --version
npm --version
npx expo-doctor
npx expo install --checkexpo-doctor procura incompatibilidades conhecidas. expo install --check
confere versões de pacotes que precisam combinar com o SDK do projeto. Em mobile,
“instalou a versão mais nova de tudo” pode ser justamente o problema; bibliotecas
nativas precisam formar um conjunto compatível.
O caminho sem framework exige Android Studio para Android, Xcode e CocoaPods para iOS, além das variáveis e SDKs da plataforma. A documentação oficial mantém o roteiro atualizado. Não copie uma lista de instalação antiga de vídeo: JDK, Gradle, Xcode e requisitos de Node mudam.
O primeiro componente: não existe texto solto
Substitua a tela inicial pelo menor resultado visível:
import { SafeAreaView, StyleSheet, Text, View } from 'react-native';
export default function HomeScreen() {
return (
<SafeAreaView style={styles.page}>
<View style={styles.card}>
<Text style={styles.eyebrow}>HOJE</Text>
<Text style={styles.title}>Passo a Passo</Text>
<Text style={styles.subtitle}>Uma ação pequena por vez.</Text>
</View>
</SafeAreaView>
);
}
const styles = StyleSheet.create({
page: { flex: 1, backgroundColor: '#101512', padding: 24 },
card: { padding: 20, borderRadius: 18, backgroundColor: '#1c251f' },
eyebrow: { color: '#9deb87', fontSize: 12, fontWeight: '700' },
title: { color: '#ffffff', fontSize: 30, fontWeight: '800', marginTop: 6 },
subtitle: { color: '#b8c4bb', fontSize: 16, marginTop: 8 },
});No React Native, texto precisa viver dentro de Text. Um texto solto dentro de
View gera erro. É uma diferença importante para a web, onde um nó de texto
pode aparecer diretamente numa div.
StyleSheet.create recebe objetos JavaScript. Propriedades usam camelCase:
backgroundColor, não background-color. Números como padding: 24 representam
unidades lógicas independentes de densidade, não 24px. Algumas propriedades
lembram CSS, mas o conjunto e os padrões não são iguais.
Faça uma mudança controlada: remova flex: 1 de page. O fundo deixa de ocupar
toda a tela porque o contêiner passa a medir apenas o conteúdo. flex é o motor
principal de layout no React Native.
Flexbox mobile: a coluna vem primeiro
Na web, flex-direction começa como row. No React Native, o padrão é
column. Pense numa lista de aplicativo: itens normalmente nascem empilhados de
cima para baixo. Para colocar título e contador lado a lado, declare a linha:
<View style={styles.row}>
<Text style={styles.habitTitle}>Beber água</Text>
<Text style={styles.streak}>7 dias</Text>
</View>const styles = StyleSheet.create({
row: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
gap: 12,
},
habitTitle: { flex: 1, color: '#ffffff', fontSize: 18 },
streak: { color: '#9deb87', fontWeight: '700' },
});Teste em tela estreita e com fonte do sistema ampliada. Um layout que cabe no simulador padrão pode cortar texto num aparelho pequeno. Evite largura fixa para conteúdo textual, deixe itens crescerem e trate quebra. Mobile não é uma única resolução.
SafeAreaView protege conteúdo de recortes e áreas do sistema em casos simples.
Aplicativos completos costumam usar uma biblioteca de safe area que oferece
insets e integração mais ampla. O conceito é o mesmo: “tela inteira” inclui
partes onde conteúdo não deveria ficar.
Estado e toque: da intenção ao novo desenho
Um hábito precisa alternar entre pendente e concluído. Pressable percebe
interações e useState guarda a informação da tela:
import { useState } from 'react';
import { Pressable, StyleSheet, Text } from 'react-native';
export function HabitButton() {
const [done, setDone] = useState(false);
return (
<Pressable
accessibilityRole="checkbox"
accessibilityState={{ checked: done }}
onPress={() => setDone((current) => !current)}
style={[styles.button, done && styles.buttonDone]}
>
<Text style={styles.buttonText}>
{done ? 'Concluído' : 'Marcar como feito'}
</Text>
</Pressable>
);
}setDone não pinta o botão diretamente. Ele solicita um novo estado; React
executa o componente outra vez; o retorno descreve como a interface deve ficar.
Essa é a mesma renderização declarativa da web.
O estilo recebe um array. Valores falsos são ignorados, então
done && styles.buttonDone acrescenta a variação somente quando necessário.
Não altere styles.button.backgroundColor na mão: estado é a fonte da verdade.
Os atributos de acessibilidade fazem diferença. Uma pessoa usando leitor de tela precisa ouvir que o controle é uma caixa de seleção e qual é seu estado. Cor e texto visual sozinhos não entregam esse contrato.
Lista grande pede FlatList, não um map infinito
Para três itens, um map dentro de ScrollView parece suficiente. Para centenas,
ele monta tudo de uma vez. FlatList renderiza uma janela de itens conforme a
rolagem:
type Habit = { id: string; title: string; done: boolean };
const habits: Habit[] = [
{ id: 'agua', title: 'Beber água', done: true },
{ id: 'leitura', title: 'Ler dez páginas', done: false },
];
<FlatList
data={habits}
keyExtractor={(item) => item.id}
renderItem={({ item }) => <HabitRow habit={item} />}
ItemSeparatorComponent={() => <View style={{ height: 12 }} />}
ListEmptyComponent={<Text>Nenhum hábito ainda.</Text>}
/>A chave precisa ser estável. Índice da posição quebra a identidade quando itens entram ou saem, o mesmo problema explicado em listas e key no React.
Evite criar funções e objetos pesados para cada item sem necessidade, mas não
comece espalhando memo por todo lado. Primeiro meça com o profiler e observe
rolagem, tempo de resposta e quantidade de renderizações.
Formulário mobile inclui teclado, foco e retorno
Um formulário que funciona com mouse pode ficar inutilizável quando o teclado
ocupa metade da tela. TextInput recebe texto, mas a experiência também depende
de rótulo, tipo de teclado, ação de retorno, foco, rolagem e mensagem de erro.
const [title, setTitle] = useState('');
const [error, setError] = useState<string | null>(null);
function submit() {
const normalized = title.trim();
if (normalized.length < 3) {
setError('Digite pelo menos três caracteres.');
return;
}
addHabit(normalized);
setTitle('');
setError(null);
}<Text style={styles.label}>Nome do hábito</Text>
<TextInput
value={title}
onChangeText={(value) => {
setTitle(value);
if (error) setError(null);
}}
onSubmitEditing={submit}
returnKeyType="done"
placeholder="Ex.: caminhar por 15 minutos"
accessibilityLabel="Nome do hábito"
style={[styles.input, error && styles.inputInvalid]}
/>
{error && <Text accessibilityLiveRegion="polite" style={styles.error}>{error}</Text>}onChangeText entrega diretamente a string, diferente do evento de input na
web. onSubmitEditing permite concluir pelo teclado. returnKeyType comunica a
ação esperada, mas não executa a validação sozinho. E placeholder não substitui
rótulo: ele some quando a pessoa começa a digitar.
Pense no teclado virtual como um móvel que entrou na sala, não como uma camada
transparente. Ele altera o espaço disponível. Telas de formulário podem usar
KeyboardAvoidingView, rolagem e ajuste específico de plataforma para manter o
campo ativo visível. Teste com o último campo da tela, mensagem de erro aberta e
fonte grande — é nesse conjunto que o botão costuma desaparecer.
Também decida o momento de validar. Erro antes do primeiro toque parece bronca; erro apenas depois de enviar pode atrasar a correção. Um padrão equilibrado é validar no envio, marcar o campo como visitado e revalidar conforme ele muda.
No aplicativo de hábitos, faça três testes manuais: envie vazio, envie dois caracteres e envie um nome com espaços nas pontas. O critério é que nada inválido entre na lista, o motivo apareça perto do campo e o teclado consiga concluir a tarefa.
Navegação: uma pilha de telas com endereço
Aplicativos precisam ir de lista para detalhe e voltar. O padrão stack se parece com uma pilha de pratos: abrir detalhe coloca uma tela no topo; voltar remove o topo e revela a anterior. Tabs organizam áreas irmãs; modal apresenta uma tarefa temporária.
Projetos Expo atuais podem usar Expo Router, no qual arquivos definem rotas. Uma estrutura possível:
app/
_layout.tsx
index.tsx
habits/
[id].tsx
settings.tsxapp/habits/[id].tsx recebe um segmento dinâmico. Navegue com um link:
import { Link } from 'expo-router';
<Link href={{ pathname: '/habits/[id]', params: { id: habit.id } }} asChild>
<Pressable>
<Text>{habit.title}</Text>
</Pressable>
</Link>Parâmetro de rota identifica o recurso; não carregue o objeto inteiro na URL. A tela de detalhe lê o id, busca o dado na fonte de verdade e trata três estados: carregando, encontrado e não encontrado.
Deep link dá endereço externo a uma tela. Planeje isso cedo se notificações, e-mail ou compartilhamento precisam abrir uma parte específica do app.
Dados de API: mobile fica offline, lento e interrompido
Buscar uma API no celular parece igual ao navegador:
const response = await fetch(`${API_URL}/habits`);
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const data: unknown = await response.json();A diferença está no ambiente. O aparelho troca Wi-Fi por rede móvel, perde sinal, suspende o aplicativo e volta minutos depois. Trate carregamento, erro, repetição, cache e cancelamento como estados de produto.
Além disso, localhost no celular aponta para o próprio celular. Sua API no
notebook precisa de um endereço acessível na rede ou de configuração específica
do emulador. Não desative segurança de transporte como correção permanente.
Variáveis expostas ao bundle do aplicativo não são segredos. URL pública e identificador podem entrar na configuração; chave privada de provedor, senha de banco e credencial administrativa ficam no back-end. A regra é simples: se o código roda no dispositivo de outra pessoa, essa pessoa pode inspecioná-lo.
Valide a resposta recebida. TypeScript some em tempo de execução; escrever
const data: Habit[] = await response.json() não transforma JSON errado em
hábitos. Um schema em runtime protege a fronteira antes de atualizar a tela.
Persistência local não é um banco remoto pequeno
Preferência de tema, onboarding concluído e cache simples podem viver em armazenamento chave-valor. Token sensível pede armazenamento seguro oferecido pela plataforma. Coleções consultadas e sincronizadas podem exigir SQLite ou uma camada de dados própria.
Escolha pelo formato e pelo risco:
preferência pequena -> chave-valor
credencial sensível -> armazenamento seguro
dados relacionais locais -> SQLite
fonte compartilhada -> API + banco no servidorPersistir não é sincronizar. Se dois aparelhos editam o mesmo hábito offline, alguém precisa definir conflito, versão e ordem. Comece com uma fonte de verdade clara e uma fila de alterações; não prometa “offline first” apenas porque um valor sobreviveu ao reinício.
Câmera, localização e notificação são permissões, não só APIs
Recurso nativo envolve três camadas: biblioteca, configuração do aplicativo e autorização do usuário. A câmera pode estar instalada no projeto e ainda não estar permitida naquele momento.
Peça permissão perto da ação e explique o benefício antes do diálogo do sistema.
Trate granted, denied e estado bloqueado. Se o usuário negar localização, o
aplicativo precisa oferecer alternativa, não uma tela quebrada.
Também teste no aparelho real. Simulador ajuda no ciclo rápido, mas câmera, notificação, desempenho, teclado, gesto e consumo de bateria ganham comportamento próprio fora dele.
Uma biblioteca nativa muda o binário. Instalar o pacote JavaScript pode exigir novo development build; recarregar o bundler não adiciona código nativo a um aplicativo já instalado. Reconhecer essa fronteira economiza horas de “o import existe, mas o módulo não foi encontrado”.
Debug sem transformar cada erro em limpeza de cache
Comece pela mensagem e pela camada:
- erro de TypeScript ocorre antes de executar;
- tela vermelha JavaScript aponta componente, importação ou estado;
- falha de bundle envolve Metro e resolução de módulo;
- falha de build envolve Gradle, Xcode, CocoaPods ou configuração nativa;
- falha no aparelho pode envolver permissão, rede, arquitetura ou versão.
Use logs para hipótese específica e remova dados sensíveis. React DevTools ajuda
a observar árvore, props e renderizações. Ferramentas da plataforma mostram logs
nativos. expo-doctor encontra incompatibilidades, mas não entende sua regra de
negócio.
Limpar cache pode corrigir estado corrompido; usá-lo como primeiro ritual apaga pistas. Reproduza com passos curtos, identifique a camada, mude uma variável e compare.
Teste o comportamento que o usuário reconhece
Uma suíte útil começa fora dos detalhes internos. O teste toca no campo pelo
rótulo, digita um hábito, pressiona o botão e procura o texto na lista. Se você
trocar useState por outro gerenciamento e o comportamento continuar, o teste
não precisa quebrar.
Divida as camadas. Funções de data, sequência e validação recebem testes unitários rápidos. Componentes recebem testes de interação. Navegação, armazenamento e rede ganham integrações com substitutos controlados. Um conjunto pequeno percorre o aplicativo instalado em Android e iOS.
Não transforme snapshot gigante na prova principal. Ele acusa qualquer mudança de estrutura e não explica se o usuário ainda consegue concluir a tarefa. Prefira consultas por papel, rótulo e texto acessível; além de teste mais resistente, isso pressiona o componente a ter semântica útil.
Os casos iniciais do Passo a Passo são: lista vazia, criação válida, erro de validação, alternância de concluído, reabertura com dado persistido e resposta da API indisponível. Depois acrescente fonte ampliada e leitor de tela no teste manual. Acessibilidade não é uma auditoria no fim; é parte do contrato que seus testes conseguem enxergar.
Performance é manter o toque respondendo
A meta inicial não é alcançar um número abstrato de frames. É evitar trabalho que bloqueia interação. Lista deve rolar, toque deve reagir e transição deve parecer contínua.
Otimize imagens no tamanho exibido, use listas virtualizadas, pagine dados e evite processamento grande durante renderização. Animações críticas podem rodar em mecanismos próprios da plataforma ou bibliotecas preparadas para isso.
Meça em build de produção e em aparelho intermediário. O modo de desenvolvimento faz trabalho extra e um celular topo de linha esconde problemas. Separe lentidão da API, JavaScript, renderização e imagem antes de escolher a correção.
Do development build à loja
O aplicativo instalado é um artefato assinado. Android e iOS usam identificador único, versão, ícone, permissões e credenciais. Expo Application Services (EAS) pode executar builds na nuvem:
npm install --global eas-cli
eas login
eas build:configure
eas build --platform androidPara iOS, conta Apple e credenciais continuam necessárias. Para lojas, prepare descrição, screenshots, classificação etária, política de privacidade e revisão. “Compilou” não significa “publicou”.
Use canais separados para desenvolvimento, homologação e produção. Cada um aponta para a API correspondente e tem identificadores controlados. Atualizações de JavaScript não podem mudar código nativo arbitrariamente; quando dependência nativa, permissão ou configuração do binário muda, gere uma nova versão.
Antes de enviar, teste instalação limpa, atualização sobre versão anterior, login expirado, rede ausente, fonte grande, tema escuro, voltar do background e permissão negada. Esses cenários encontram mais defeitos reais que tocar apenas o caminho feliz no simulador.
A ordem de estudo que termina num aplicativo publicável
Siga a trilha de React Native nesta sequência:
- ambiente, Expo e primeiro componente;
View,Text,Image,PressableeTextInput;- StyleSheet, Flexbox, teclado e diferentes telas;
- props, estado, formulários e lista;
- navegação e parâmetros;
- API, validação, cache e estados de rede;
- armazenamento, permissão e recurso nativo;
- testes, acessibilidade e performance;
- development build, assinatura e publicação.
Para transformar essa sequência numa entrega única, siga o tutorial App mobile com React Native, Expo e TypeScript. Ele constrói um rastreador de hábitos com rotas, persistência, API e testes.
A missão deste guia é criar o Passo a Passo com duas telas. Na primeira, o usuário cadastra e marca hábitos. Na segunda, vê o detalhe e a sequência de dias. Os dados devem sobreviver ao reinício; lista vazia, teclado, erro e carregamento precisam ter interface; leitor de tela deve anunciar cada controle.
O critério final é concreto: outra pessoa instala o build num Android ou iPhone, cria um hábito, fecha o app, abre de novo e encontra o dado. Depois ela nega uma permissão opcional e continua usando o núcleo. Se isso funciona fora do seu simulador, você não construiu apenas componentes — construiu um aplicativo.
Trilha
React Native
O mesmo JavaScript rodando no celular, do primeiro componente à publicação nas lojas.
app react native
App mobile com React Native, Expo e TypeScript
Perguntas frequentes
Preciso saber React antes de aprender React Native?
React Native cria aplicativo nativo de verdade?
Expo é obrigatório para usar React Native?
Consigo publicar o mesmo código no Android e no iOS?
Preciso de Mac para desenvolver React Native?
JavaScript ou TypeScript para React Native?
Fontes consultadas
- React Native Docs — Get Started — reactnative.dev
- React Native Docs — Get Started Without a Framework — reactnative.dev
- React Native Docs — Core Components and Native Components — reactnative.dev
- React Native Docs — Performance Overview — reactnative.dev
- Expo Docs — Tutorial — docs.expo.dev
- Expo Docs — EAS Build — docs.expo.dev
