Dia 9 — Navegação entre telas

Informatica · Conteudo · publicado em 05/10/2026
Dia 9 de 16

Navegação entre telas

Aula 1

React Navigation e o Navigator

React Navigation e o Navigator

Trocar de tela sem recarregar o app é o que a navegação resolve. A peça

principal é a pilha: cada tela que abre fica por cima da anterior, e voltar

é retirar a última. O pacote que implementa isso no React Native atual é o

React Navigation, e o componente que cria a pilha vem do módulo de pilha nativa

através de createNativeStackNavigator.

A montagem tem três peças e a ordem importa:

  1. O NavigationContainer — o contêiner que segura o estado da navegação.
  2. O Stack.Navigator — declara que tipo de navegação é, pilha ou aba.
  3. O Stack.Screen — uma linha por tela, com name e o componente.
const Stack = createNativeStackNavigator();

function Rotas() {
  return (
    <NavigationContainer>
      <Stack.Navigator>
        <Stack.Screen name="Pedidos" component={TelaPedidos} />
        <Stack.Screen name="Detalhe" component={TelaDetalhe} />
      </Stack.Navigator>
    </NavigationContainer>
  );
}

O Stack.Screen é só declaração: ele não é a tela. name é a chave usada

para navegar e o que aparece no cabeçalho por padrão; component é a função que

devolve o conteúdo. Isso explica um comportamento que confunde: uma tela que

define options recebe o objeto { navigation, route } nas props, e o mesmo

acesso por hook funciona em qualquer ponto abaixo do contêiner.

O exemplo desta página isola a linha de declaração em um array e imprime o que

ela carrega: os três name — Pedidos, Detalhe, Editar —, o primeiro como

primeira tela da pilha, e o options da primeira como `{ title: 'Meus pedidos'

}. Uma tela sem options não tem título próprio no objeto: o name` é o que

vira título, e é por isso que a segunda e a terceira linhas do array não trazem

nada além do name e do component.

navegar e voltar

navigation.navigate('Detalhe') empilha; navigation.goBack() desempilha. A

diferença que importa: navigate para uma tela que já está na pilha volta

para ela em vez de duplicar, e é por isso que ele é o método certo para menu.

push, que empilha sempre, é o certo para uma tela que pode abrir várias vezes,

como uma conversa de chat — onde voltar deve desfazer uma instância, não todas.

A diferença aparece inteira na saída do exemplo. A primeira navegação empilha

Detalhe e a profundidade vai para 2; a segunda empilha Editar e a

profundidade vai para 3. Os dois goBack devolvem a pilha ao array `[ 'Pedidos'

]` — o que prova que voltar é retirar a última e não sair da tela. E o terceiro

goBack não falha: devolve "nao ha tela anterior", porque a pilha já está no

começo. Uma pilha de tamanho 1 não tem o que desempilhar.

O par navigate e empilhar resolve a diferença do parágrafo de cima com um

número: navegar para Pedidos quando ele já está na pilha devolve a

profundidade 3, sem duplicar; empilhar a mesma tela devolve 4. A primeira

volta, a segunda empilha. É a mesma tela e o mesmo nome — o que muda é o método,

e é o método que decide se voltar desfaz uma instância ou todas.

navigation.setOptions muda o cabeçalho da tela atual sem declarar outra

tela. options na declaração serve para o padrão, e o setOptions serve para

o que depende do dado da tela: título com o nome do item, botão de editar que

só existe naquele registro. É a propriedade que resolve o "o cabeçalho precisa

saber o que a tela carregou".

O exemplo chama cabecalhoDe duas vezes e o objeto options muda junto: com

podeEditar: true o resultado é { title: 'ana', headerRight: 'editar' }; com

podeEditar: false é { title: 'bruno', headerRight: null }. O mesmo cabeçalho,

duas telas, e o botão de editar existe em uma e não na outra — sem declarar

nenhuma tela nova.

onde a pilha é montada

O contêiner de navegação fica no ponto de entrada do aplicativo, e as telas

ficam em arquivos separados. O erro de estrutura que trava a navegação não é de

sintaxe: é o contêiner montado dentro de uma tela. Assim a pilha é recriada

quando a tela monta, o histórico se perde ao voltar, e o comportamento muda

conforme de onde se chegou. A pilha tem um dono, e esse dono é o componente

raiz do aplicativo.

O JSON.stringify do exemplo mostra essa estrutura na ordem que importa: o

contêiner primeiro, e dentroDele com os nomes das três telas. O array de telas

é filho do contêiner — não de nenhuma tela. Inverter a ordem não é um detalhe

de leitura: é o que faz o goBack do parágrafo anterior perder o histórico.

A tela, do lado dela, é uma função que recebe { navigation, route } e devolve

conteúdo. O exemplo monta TelaDetalhe com as duas props e imprime o nome da

rota — Detalhe — que é o texto que a tela renderiza. route traz nome e

parâmetros, navigation traz o navigate e o goBack, e nenhum dos dois é

passado por outra tela: chegam na prop porque estão no contêiner.

Exemplo

// A navegacao e uma PILHA: `navigate` empilha, `goBack` desempilha, e
// `navigate` para uma tela ja aberta volta para ela em vez de duplicar.
// O exemplo monta o array de telas e simula a pilha.
const { View, Text, StyleSheet } = require('react-native');

const estilos = StyleSheet.create({ tela: { padding: 16 } });

// A linha de declaracao de tela e so isso: `name` e a chave, `component` e
// a funcao que devolve o conteudo.
const telas = [
  { name: 'Pedidos', component: 'TelaPedidos', options: { title: 'Meus pedidos' } },
  { name: 'Detalhe', component: 'TelaDetalhe' },
  { name: 'Editar', component: 'TelaEditar' },
];

console.log('telas declaradas:', telas.map((t) => t.name));
console.log('a primeira tela da pilha:', telas[0].name);
console.log('`options` serve para o padrao do cabecalho:', telas[0].options);
console.log('sem `options`, o `name` vira o titulo');

// A pilha e um array: `navigate` empilha, `goBack` desempilha.
function criarNavegacao(inicial) {
  const pilha = [inicial];
  return {
    navegar(para) {
      if (pilha[pilha.length - 1] === para) return 'ja estava na tela ' + para;
      pilha.push(para);
      return 'abriu ' + para;
    },
    voltar() {
      if (pilha.length <= 1) return 'nao ha tela anterior';
      const saiu = pilha.pop();
      return 'voltou de ' + saiu + ' para ' + pilha[pilha.length - 1];
    },
    empilhar(para) {
      pilha.push(para);
      return 'empilhou ' + para;
    },
    topo() {
      return pilha[pilha.length - 1];
    },
    profundidade() {
      return pilha.length;
    },
    historico() {
      return pilha.slice();
    },
  };
}

const nav = criarNavegacao('Pedidos');
console.log(nav.navegar('Detalhe'));
console.log('topo da pilha:', nav.topo(), '| profundidade:', nav.profundidade());
console.log(nav.navegar('Editar'));
console.log('topo da pilha:', nav.topo(), '| profundidade:', nav.profundidade());
console.log(nav.voltar());
console.log(nav.voltar());
console.log('a pilha voltou ao inicio:', nav.historico());
console.log(nav.voltar());

// `navigate` para tela ja aberta volta em vez de duplicar; `push`
// empilharia sempre.
const nav2 = criarNavegacao('Pedidos');
console.log(nav2.navegar('Detalhe'));
console.log(nav2.navegar('Pedidos'), '| profundidade:', nav2.profundidade(), '-> nao duplicou');
console.log(nav2.empilhar('Pedidos'), '| profundidade:', nav2.profundidade(), '-> `push` empilha sempre');

// `setOptions` muda o cabecalho da tela atual com o dado que ela carregou.
let opcoes = {};
function cabecalhoDe(dados) {
  opcoes = Object.assign({}, opcoes, {
    title: dados.cliente,
    headerRight: dados.podeEditar ? 'editar' : null,
  });
  return 'cabecalho: ' + dados.cliente;
}

console.log(cabecalhoDe({ cliente: 'ana', podeEditar: true }));
console.log('opcoes da tela atual:', opcoes);
console.log(cabecalhoDe({ cliente: 'bruno', podeEditar: false }));
console.log('com `setOptions`, o cabecalho muda conforme o dado:', opcoes);

// A arvore de uma tela: ela recebe `navigation` e `route`.
function TelaDetalhe(props) {
  return <Text style={estilos.tela}>{props.route.name}</Text>;
}

const arvore = TelaDetalhe({ navigation: nav, route: { name: 'Detalhe', params: { id: 'p41' } } });
console.log('tipo da tela:', arvore.type, '| nome na rota:', arvore.props.children);
console.log('`route` traz nome e parametros; `navigation` traz o navigate e o goBack');

// A pilha tem UM dono: o container fica na entrada do aplicativo, e nunca
// dentro de uma tela.
const entradaDoApp = { container: 'contêiner de navegação', dentroDele: telas.map((t) => t.name) };
console.log('estrutura da entrada:', JSON.stringify(entradaDoApp));
console.log('container dentro de uma tela faz voltar perder o historico');

Saída real

telas declaradas: [ 'Pedidos', 'Detalhe', 'Editar' ]
a primeira tela da pilha: Pedidos
`options` serve para o padrao do cabecalho: { title: 'Meus pedidos' }
sem `options`, o `name` vira o titulo
abriu Detalhe
topo da pilha: Detalhe | profundidade: 2
abriu Editar
topo da pilha: Editar | profundidade: 3
voltou de Editar para Detalhe
voltou de Detalhe para Pedidos
a pilha voltou ao inicio: [ 'Pedidos' ]
nao ha tela anterior
abriu Detalhe
abriu Pedidos | profundidade: 3 -> nao duplicou
empilhou Pedidos | profundidade: 4 -> `push` empilha sempre
cabecalho: ana
opcoes da tela atual: { title: 'ana', headerRight: 'editar' }
cabecalho: bruno
com `setOptions`, o cabecalho muda conforme o dado: { title: 'bruno', headerRight: null }
tipo da tela: Text | nome na rota: Detalhe
`route` traz nome e parametros; `navigation` traz o navigate e o goBack
estrutura da entrada: {"container":"contêiner de navegação","dentroDele":["Pedidos","Detalhe","Editar"]}
container dentro de uma tela faz voltar perder o historico
Aula 2

Parâmetros entre telas e abas

Parâmetros entre telas e abas

Parâmetro de rota é o dado que uma tela entrega à próxima, e ele vai em

navigate no segundo argumento. O caminho completo é declaração, envio e

leitura, e cada parte tem o nome que importa:

// envio
navigation.navigate('Detalhe', { id: item.id });

// leitura, dentro da tela de destino
const { id } = route.params;

useRoute e useNavigation são os hooks que entregam esses dois objetos em

qualquer ponto da árvore, e useNavigation devolve também goBack,

setOptions, reset e push. São eles que resolvem o problema do repasse de

propriedades em navegação: passar navigation de mão em mão por três

componentes intermediários não é necessário, porque o hook alcança o contêiner

de onde a tela está.

O exemplo desta página percorre o caminho inteiro, e o console do envio mostra

a forma exata: navigate(Detalhe, {"id":"p41"}). O nome da tela vem primeiro, o

objeto de parâmetro vem segundo, e o objeto tem uma chave só. É esse texto que

vira o segundo argumento da chamada real.

O erro que trava a tela em branco é sempre o mesmo: route.params lido quando

params é undefined. Acontece quando a tela é aberta por outro caminho que

não passou parâmetro — notificação, restauração de sessão, link direto. A

alternativa segura é valor padrão: const id = route.params && route.params.id,

e tratar o ausente como estado vazio em vez de deixar a leitura estourar.

Os três console de rota do exemplo mostram exatamente esse problema e a

solução lado a lado. Com {"name":"Detalhe","params":{"id":"p41"}} a tela

mostra o pedido p41; com params undefined e com a chave params

inteiramente ausente, a tela mostra "pedido nao informado" — e não quebra. São

três caminhos de entrada e uma tela só, e a leitura protegida é o que faz os

dois últimos existirem.

o que parâmetro não deve ser

Parâmetro de rota é para o identificador, não para o registro inteiro.

Passar o objeto completo funciona e quebra depois: o que ficou guardado na rota

é uma cópia do momento da navegação, e se o registro mudar no servidor a tela

continua mostrando a cópia velha, sem nenhuma requisição para detectar. Passar

id obriga a tela a buscar, e é o que garante que o que aparece é o estado

atual.

O exemplo põe as duas formas lado a lado, e a diferença de tamanho é o argumento

inteiro: mandando só o id o objeto sai como {"id":"p41"}; mandando o

registro inteiro ele sai com cliente e total junto. A cópia velha é esse

segundo objeto — ele viaja inteiro pela navegação e chega na tela de destino

com os valores que tinha no momento do toque. Nada ali pede o estado atual ao

servidor.

A exceção que confirma a regra: passar o que é caro de buscar e que a tela não

precisa atualizar — o nome do cliente, para o cabeçalho aparecer sem esperar a

rede. É um cache deliberado de uma tela, não um modelo de dados.

abas

O construtor de abas troca a pilha por uma barra, e a montagem é a mesma forma

com outro construtor: contêiner, Tab.Navigator, Tab.Screen. A diferença que

importa está em dois detalhes. Abas mantêm estado: o componente monta uma vez e

a troca de aba não desmonta, então o filtro digitado na aba de lista continua

lá quando se volta. E tabBarIcon recebe o foco da aba, o que permite trocar

o ícone conforme ela está ativa.

O exemplo descreve cada aba pelo que ela contém por dentro, e a distinção fica

na string: "Pedidos" tem a pilha ListaPedidos > DetalhePedido, e "Perfil" tem

só DadosPerfil. São duas pilhas independentes, e é por isso que voltar dentro

da aba de pedidos não troca de aba — o goBack opera na pilha interna, que é a

que pertence à aba ativa.

A combinação que aparece em quase todo aplicativo é pilha por baixo e abas por

dentro: cada aba tem sua própria pilha, então voltar dentro da aba de pedidos

não leva para a aba de perfil. É um Navigator dentro de um Screen, e a

ordem importa — a aba é o contêiner de fora.

A manutenção de estado aparece no fim da saída: as três chamadas de

TrocaDeAba registram Pedidos > Perfil > Pedidos, e a aba de pedidos aparece

2 vezes no registro de montagem — o que a linha final traduz como "o estado

continua". Voltar a uma aba não remonta a tela; devolve a tela que ainda estava

montada, com o filtro digitado e a rolagem no lugar. É a diferença entre a aba

e o goBack: o goBack desempilha, a troca de aba não.

Exemplo

// Parametro de rota e o IDENTIFICADOR que uma tela entrega a outra, e
// ele vai no segundo argumento da navegacao. O exemplo mostra o envio,
// a leitura e o que acontece quando o parametro nao vem.
const { View, Text, StyleSheet } = require('react-native');

const estilos = StyleSheet.create({ linha: { padding: 12 } });

// (1) envio: o segundo argumento do `navigate` leva o parametro.
function ItemPedido(props) {
  return (
    <View style={estilos.linha}>
      <Text onPress={() => props.navigation.navigate('Detalhe', { id: props.pedido.id })}>
        {props.pedido.cliente}
      </Text>
    </View>
  );
}

// (2) leitura, na tela de destino. O `params` pode nao vir, e o exemplo
// trata o ausente em vez de deixar a leitura estourar.
function TelaDetalhe(props) {
  const rota = props.route;
  const params = rota.params;
  const id = params && params.id ? params.id : null;

  if (id === null) {
    return <Text style={estilos.linha}>pedido nao informado</Text>;
  }
  return <Text style={estilos.linha}>pedido {id}</Text>;
}

const pedido = { id: 'p41', cliente: 'ana', total: 90 };
const lista = ItemPedido({ pedido, navigation: { navigate: () => {} } });
console.log('item montado:', lista.type, '| texto:', lista.props.children.props.children);

// O envio: e o `navigate` com o parametro.
function enviar(destino, parametros) {
  return 'navigate(' + destino + ', ' + JSON.stringify(parametros) + ')';
}
console.log('envio:', enviar('Detalhe', { id: pedido.id }));

// (3) `route.params` ausente e o caminho seguro de leitura.
const rotaCompleta = { name: 'Detalhe', params: { id: 'p41' } };
const rotaVazia = { name: 'Detalhe', params: undefined };
const rotaDeNotificacao = { name: 'Detalhe' };

for (const rota of [rotaCompleta, rotaVazia, rotaDeNotificacao]) {
  const arvore = TelaDetalhe({ route: rota });
  console.log('rota', JSON.stringify(rota), '-> tela:', arvore.props.children);
}

// (4) o hook entrega `route` e `navigation` em qualquer ponto da arvore,
// sem passar de mao em mao.
function dentroDeUmComponente() {
  const params = rotaCompleta.params;
  return params.id;
}
console.log('o mesmo dado, lido de dentro do componente:', dentroDeUmComponente());
console.log('metodos de `navigation`: navigate, goBack, push, reset, setOptions');

// (5) parametro de rota e IDENTIFICADOR, nao o registro inteiro.
console.log('mandando so o id:', JSON.stringify({ id: pedido.id }));
console.log('mandando o registro inteiro:', JSON.stringify(pedido), '-> uma copia velha da tela');
console.log('quem recebe o id precisa buscar, e assim mostra o estado atual');

// (6) abas: cada aba e um `Navigator` com pilha propria.
const abas = [
  { name: 'Pedidos', pilha: ['ListaPedidos', 'DetalhePedido'] },
  { name: 'Perfil', pilha: ['DadosPerfil'] },
];
for (const aba of abas) {
  console.log('aba', aba.name, '| pilha interna:', aba.pilha.join(' > '));
}
console.log('voltar dentro da aba de pedidos nao troca de aba');
console.log('a aba e o container de fora; a pilha e a de dentro');

// (7) aba mantem estado: trocar de aba nao remonta a tela.
const visitadas = [];
function TrocaDeAba(props) {
  visitadas.push(props.aba);
  return <Text>{props.aba}</Text>;
}
TrocaDeAba({ aba: 'Pedidos' });
TrocaDeAba({ aba: 'Perfil' });
TrocaDeAba({ aba: 'Pedidos' });
console.log('ordem das visitas:', visitadas.join(' > '));
console.log('a aba de pedidos foi montada', visitadas.filter((a) => a === 'Pedidos').length, 'vezes, e o estado continua');

Saída real

item montado: View | texto: ana
envio: navigate(Detalhe, {"id":"p41"})
rota {"name":"Detalhe","params":{"id":"p41"}} -> tela: [ 'pedido ', 'p41' ]
rota {"name":"Detalhe"} -> tela: pedido nao informado
rota {"name":"Detalhe"} -> tela: pedido nao informado
o mesmo dado, lido de dentro do componente: p41
metodos de `navigation`: navigate, goBack, push, reset, setOptions
mandando so o id: {"id":"p41"}
mandando o registro inteiro: {"id":"p41","cliente":"ana","total":90} -> uma copia velha da tela
quem recebe o id precisa buscar, e assim mostra o estado atual
aba Pedidos | pilha interna: ListaPedidos > DetalhePedido
aba Perfil | pilha interna: DadosPerfil
voltar dentro da aba de pedidos nao troca de aba
a aba e o container de fora; a pilha e a de dentro
ordem das visitas: Pedidos > Perfil > Pedidos
a aba de pedidos foi montada 2 vezes, e o estado continua