Pular para o conteúdo
Cursos

DevClub

LógicaFront-endBack-endMobile

IA Club

IA na prática
Estudar programaçãoEstudar IA

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.

5 lições em trilha6 artigos no guiaAtualizado em agosto de 2026

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:

bash
npx create-expo-app@latest passo-a-passo
cd passo-a-passo
npm start

O 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:

bash
node --version
npm --version
npx expo-doctor
npx expo install --check

expo-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:

tsx
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:

tsx
<View style={styles.row}>
  <Text style={styles.habitTitle}>Beber água</Text>
  <Text style={styles.streak}>7 dias</Text>
</View>
tsx
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:

tsx
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:

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

tsx
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);
}
tsx
<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.

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:

text
app/
  _layout.tsx
  index.tsx
  habits/
    [id].tsx
  settings.tsx

app/habits/[id].tsx recebe um segmento dinâmico. Navegue com um link:

tsx
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:

tsx
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:

text
preferência pequena       -> chave-valor
credencial sensível       -> armazenamento seguro
dados relacionais locais  -> SQLite
fonte compartilhada       -> API + banco no servidor

Persistir 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:

bash
npm install --global eas-cli
eas login
eas build:configure
eas build --platform android

Para 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:

  1. ambiente, Expo e primeiro componente;
  2. View, Text, Image, Pressable e TextInput;
  3. StyleSheet, Flexbox, teclado e diferentes telas;
  4. props, estado, formulários e lista;
  5. navegação e parâmetros;
  6. API, validação, cache e estados de rede;
  7. armazenamento, permissão e recurso nativo;
  8. testes, acessibilidade e performance;
  9. 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.

Ver a trilha
  1. 01Primeiro app React Native com Expo e TypeScript
  2. 02Componentes e estilos no React Native
  3. 03Estado e formulários no React Native
  4. 04Listas e navegação no React Native com Expo Router
  5. 05APIs e armazenamento no React Native com Expo

app react native

Perguntas frequentes

Preciso saber React antes de aprender React Native?
Sim. Componentes, props, estado, hooks e renderização são o centro dos dois. Você não precisa dominar React para web inteiro, mas aprender esses fundamentos primeiro evita confundir uma dúvida de React com uma dúvida de Android ou iOS.
React Native cria aplicativo nativo de verdade?
Sim. Componentes como View e Text viram componentes das plataformas, não uma página HTML dentro de um navegador. Seu código JavaScript coordena a interface e pode chamar APIs nativas, diretamente ou por módulos.
Expo é obrigatório para usar React Native?
Não, mas a própria documentação do React Native recomenda começar com um framework. Expo oferece roteamento, bibliotecas, desenvolvimento e build integrados. O fluxo sem framework continua disponível quando o projeto precisa controlar diretamente os projetos Android e iOS.
Consigo publicar o mesmo código no Android e no iOS?
Grande parte do código pode ser compartilhada, mas não prometa cem por cento. Permissões, convenções de interface, APIs, tamanho de tela, teclado e processo de loja diferem. O projeto saudável compartilha regra e componentes, mantendo adaptações explícitas quando a plataforma pede.
Preciso de Mac para desenvolver React Native?
Para desenvolver e testar Android, não. Para executar o simulador iOS e compilar localmente com Xcode, sim. Serviços de build em nuvem podem gerar o binário iOS, mas conta Apple, certificados, testes reais e publicação continuam fazendo parte do processo.
JavaScript ou TypeScript para React Native?
TypeScript é uma boa escolha para projetos novos, principalmente em props, navegação e respostas da API. Se tipos ainda escondem o conceito em vez de ajudar, faça primeiro componentes pequenos em JavaScript e migre assim que props e estado estiverem claros.

Fontes consultadas

  1. React Native Docs — Get Started — reactnative.dev
  2. React Native Docs — Get Started Without a Framework — reactnative.dev
  3. React Native Docs — Core Components and Native Components — reactnative.dev
  4. React Native Docs — Performance Overview — reactnative.dev
  5. Expo Docs — Tutorial — docs.expo.dev
  6. Expo Docs — EAS Build — docs.expo.dev