Padrões de navegação do React Native: stacks, tabs e drawer

No dia a dia, usamos uma infinidade de aplicativos, seja para pedir comida, acessar nossas contas bancárias, pedir um transporte ou acessar as redes sociais.
E sabe o que todos eles têm em comum? Todos usam algum tipo de navegação entre as telas da aplicação.
Quando usamos o Spotify, é fácil navegar da aba de “Início” para a nossa “Biblioteca” na barra de inferior.
Ao pedir uma comida no iFood, passamos por uma sequência de passos que se sobrepõem até concluirmos o pedido.
Quando queremos acessar uma opção extra do nosso banco, clicamos no ícone para abrir um menu lateral ou até mesmo fazemos o gesto de arrastar a tela para o lado para que o menu se abra.
Todos esses exemplos são formas de navegar em uma app. E saber como organizar a navegação da melhor forma possível é um diferencial para conseguir uma experiência de usuário(a) mais intuitiva e fluida.
Por isso, neste artigo, vamos explorar o maravilhoso mundo da navegação entre telas usando o React Native e o Expo, e iremos:
- Conhecer a biblioteca
expo-router
; - Entender como funcionam as três principais abordagens de navegação: stacks (pilhas), tabs (guias) e drawer (gaveta);
- Explorar exemplos práticos para implementar cada abordagem;
- Descobrir as melhores situações em que podemos aplicar cada tipo de navegação.
Então, vem comigo!
Explorando a biblioteca expo-router do Expo
Antes de explorar os tipos de navegação, vamos dar um passo para trás e conhecer um pouco mais sobre o Expo, pois é com base neste framework que iremos basear tudo o que iremos ver daqui em diante.
O Expo, possui um conjunto de ferramentas que simplifica o desenvolvimento de aplicativos mobile com React Native, e nessa simplicidade ele elimina a necessidade de configurações complicadas que teríamos com o Xcode (para iOS) e Android Studio (para Android).
Dentro desse framework, existe uma biblioteca chamada expo-router, que cuida especificamente do sistema de navegação.
Ela funciona de maneira muito similar ao sistema de rotas do Next.js, onde a estrutura de pastas e arquivos dentro da pasta app
, define automaticamente as rotas do aplicativo.
Se quiser conhecer mais sobre o sistema de rotas do Next.js, recomendo que você leia o artigo Roteamento eficiente com Next.js: descobrindo o App Router.
Beleza, mas como isso funciona na prática?
No expo-router
, cada arquivo com extensão .tsx
ou .jsx
dentro da pasta app
representa uma tela no aplicativo.

Na ilustração acima, temos uma pasta app
e dentro dela quatro arquivos:
index.tsx
, que representa a página inicial do aplicativo (“/”);sobre.tsx
, que representa a tela de perfil (“/sobre”);configuracoes.tsx
, que representa a página de configurações (“/configuracoes”);_layout.tsx
, responsável por organizar todas as páginas.
É no arquivo _layout.tsx
que nós definimos qual ou quais abordagens de navegação vamos utilizar para nosso aplicativo. E é exatamente sobre essas abordagens que começaremos a explorar agora!

Navegação com Stacks
A primeira abordagem que vamos conhecer é a navegação por Stack.
Para entender seu funcionamento, vamos imaginar que você tem algumas peças de roupa que foram colocadas no varal para secar, assim que as peças ficam secas, você as recolhe, dobra e começa a empilhar uma sobre a outra para guardar posteriormente.
É exatamente da mesma forma que a navegação por Stacks funciona, ao acessar um aplicativo somos redirecionados para a tela inicial, se quisermos acessar outras páginas, começaremos a ter um empilhamento de telas, onde a próxima tela acessada ficará sobreposta à tela anterior.
Basicamente, assim como empilhamos as roupas secas, também empilhamos as telas de um app à medida que vamos navegando entre as páginas.
Com essa abordagem, montamos as telas e empilhamos elas assim que acessamos uma nova página, mas essa pilha também pode ser desfeita!
O ciclo de vida de uma tela termina quando ela é removida da pilha, ou seja, quando voltamos para a tela anterior.
Novamente podemos pensar na analogia da pilha de roupas secas, assim que guardamos uma das peças da pilha ela deixa de fazer parte do monte.
Beleza, entendemos que essa pilha pode crescer e diminuir, mas enquanto ela não diminui, será que isso afeta o desempenho da minha aplicação?
Em fluxos longos, onde várias telas são acessadas sem um controle adequado, o acúmulo pode resultar em lentidão e até em falhas no aplicativo devido à sobrecarga de memória.
Mas calma, é possível fazer algumas otimizações para melhorar a performance da navegação em Stacks, como, configurar corretamente a desmontagem de telas quando não forem mais necessárias evita que o aplicativo mantenha componentes inativos ocupando memória, ou utilizar navegação condicional, onde apenas as telas essenciais permanecem carregadas, e outras sejam reconstruídas sob demanda.
Com essas otimizações, é possível equilibrar a experiência da pessoa usuária com um desempenho eficiente, garantindo que a aplicação se mantenha rápida e responsiva, independentemente do volume de navegação realizado.
Agora que exploramos a teoria, vamos analisar um exemplo! Preparei um código com a mesma estrutura que vimos anteriormente, onde temos as telas de página inicial, de perfil e configurações. Você pode acessar o código neste Gist do GitHub.
No código, todas as páginas possuem a mesma estrutura e os mesmos estilos. A única coisa que muda é o código no arquivo _layout.tsx
, pois como mencionei, é nele que definimos como a navegação deve acontecer.
Neste arquivo, utilizamos a tag <Stack/>
importada do expo-router
para sinalizar que queremos usar a navegação em pilha dentro da pasta app
.
Por apenas usar a tag <Stack/>
sem nenhum parâmetro ou informação adicional, o arquivo que será exibido ao abrir o aplicativo é o que possui o nome index
, e a partir dele a aplicação fará a navegação entre as demais rotas definidas pela tag <Link>
em cada uma das telas.
Ao rodarmos o código acima, o resultado em tela será:

Ao navegar entre as telas percebemos o efeito do empilhamento na prática. Acessamos inicialmente a “Página inicial”, ao clicar em um link a tela para onde somos redirecionados é colocada acima da tela anterior e se clicarmos no ícone de seta que aparece no canto superior esquerdo da tela ao lado do nome do arquivo, fica ainda mais evidente o caminho percorrido para acessar um determinado conteúdo.
Esse modelo é útil para fluxos onde a pessoa usuária precisa avançar e retroceder entre as telas da aplicação.
Por exemplo, suponha que você precisa preencher um formulário de cadastro em um aplicativo bancário.
Primeiro, você insere seus dados pessoais, depois avança para adicionar informações financeiras e, por fim, revisa tudo antes de concluir.
Esse é um fluxo típico de navegação em pilha (Stack), onde cada nova tela empilha sobre a anterior, permitindo que a pessoa usuária volte para conferir ou corrigir algo.
Navegação com Tabs
Já conhecemos a navegação por Stacks, agora chegou a vez da navegação por Tabs.
Para conseguir compreender melhor o funcionamento desse tipo de navegação, vou retomar o meu tempo de estudante (já faz bastante tempo) e resgatar o meu velho companheiro, o fichário.
Dentro dele eu carregava as anotações de todas as matérias e elas não ficavam todas misturadas, eu sempre colocava divisórias para organizar e separar o conteúdo de cada disciplina.
O funcionamento das Tabs é semelhante à função das divisórias do fichário. Com a navegação por Tabs, criamos uma guia que fica no canto inferior da tela e nela temos algumas abas que ao acessar nos redirecionam para uma tela específica com um conteúdo próprio.
Nesta abordagem, não empilhamos as telas como acontece nas Stacks. Por esse motivo temos uma mudança bem clara no ciclo de vida de uma página.
Aqui, a primeira vez que uma aba é acessada, a tela correspondente é montada. Esse processo carrega os componentes e executa qualquer efeito colateral inicial.
Depois de montada, a tela permanece ativa na memória do aplicativo, a menos que seja explicitamente configurada para ser desmontada.
Mas isso não iria impactar a memória do dispositivo? Sim, mas para economizar recursos, algumas telas menos usadas podem ser desmontadas automaticamente para liberar memória.
Um fator bem relevante também para esse tipo de navegação é que por permanecerem montadas, as telas não precisam ser recarregadas e isso faz com que tenhamos uma melhor performance, já que apenas as abas acessadas mais recentemente permanecem armazenadas na memória, reduzindo o consumo de recursos do dispositivo e garantindo uma navegação ágil.
Por isso, esse tipo de navegação é bastante utilizado, por exemplo, para organizar seções distintas de um aplicativo, garantindo uma troca ágil entre elas.
Um ótimo exemplo é um aplicativo de música como o Spotify, onde é possível navegar entre as abas “Início”, “Pesquisar” e “Bibliotecas” rapidamente.
Para conseguir visualizar como essa navegação funciona na prática, vamos retomar o exemplo que usamos na navegação por Stacks e fazer alguns ajustes.
O código pode ser acessado neste Gist do GitHub
Como o objetivo é mostrar a navegação por Tabs, as tags <Link>
foram removidas de cada uma das páginas.
Agora, a maior mudança, com certeza vai ser o arquivo _layout.tsx
, onde deixamos de ter apenas uma tag <Stack/>
e passamos a ter um código com algumas personalizações que permitem adicionar ícones, definir estilos de cores para as abas e implementar títulos personalizados.
Se você não entendeu o que o código acima faz, fique em paz, pois eu vou explicar passo a passo agora mesmo!
Dentro da tag <Tabs>
eu utilizei a propriedade screenOptions
para definir os estilos globais para todas as guias como a cor azul para a aba ativa com o código tabBarActiveTintColor: "#007AFF"
e o tamanho dos textos das abas com a opção tabBarLabelStyle: { fontSize: 16 }
.
Dentro da tag <Tabs>
cada aba é criada usando a tag <Tabs.Screen />
que possui as propriedades name
que indica qual é o arquivo que deve ser aberto na aplicação, title
que define o texto exibido na guia e a tabBarIcon
que implementa um ícone basedo no pacote Feather
do expo/vector
.
O resultado é o seguinte:

Incrível não é mesmo? Mas ainda não acabou, ainda temos mais uma forma de transitar entre telas!
Navegação com Drawer
A última abordagem que vamos tratar neste texto é a navegação por Drawer, que cria um menu lateral deslizante, que permanece oculto até ser acionado por um gesto de deslizar ou pelo clique de um ícone específico.
Basicamente seu funcionamento é semelhante a uma gaveta de uma escrivaninha, para termos acesso aos objetos que estão dentro dela, precisamos abrir a gaveta, e ao pegar o que queremos, ela é fechada.
Da mesma maneira, abrimos o menu lateral e ao acessar ao clicar em uma opção dentro do menu, a tela é renderizada e o menu automaticamente é fechado.
Diferente da navegação por Stacks e por Tabs, para utilizar o Drawer são necessárias algumas bibliotecas adicionais:
@react-navigation/drawer
: fornece os componentes e funcionalidades específicos para a navegação do tipo drawer;react-native-gesture-handler
: melhora o manuseio de gestos no React Native, permitindo que interações como deslizar para abrir o menu sejam processadas de forma nativa, resultando em uma experiência mais fluida;react-native-reanimated
: facilita a criação de animações complexas e de alto desempenho, essenciais para as transições suaves do menu drawer.
Com essas bibliotecas, as animações sejam executadas na thread nativa, evitando bloqueios e garantindo fluidez nas interações.
Mas como eu posso instalar essas dependências? Para isso, você pode usar o seguinte comando no terminal:
npx expo install @react-navigation/drawer react-native-gesture-handler react-native-reanimated
Ao aplicar essa abordagem, o ciclo de vida das telas tem comportamento similar ao da navegação por Tabs.
Isto é, quando abrimos uma tela pela primeira vez, ela é montada e apenas se uma configuração for definida essa tela será desmontada.
Assim como nas Tabs, isso pode ser vantajoso para manter o estado das telas. Dessa forma, sempre que alterarmos de tela, reaproveitamos a tela que já estava carregada e apenas fazemos alterações em locais específicos.
E a dúvida ressurge: mas manter tudo sempre carregado não é ruim para a performance?
As telas são mantidas em segundo plano, evitando a necessidade de recriação constante, o que melhora a experiência da pessoa usuária em dispositivos com menor poder de processamento.
Do mesmo modo, usar as bibliotecas react-native-gesture-handler
e react-native-reanimated
otimiza a performance, pois essas ferramentas permitem que os gestos e animações sejam processados na camada nativa do dispositivo, garantindo uma navegação mais fluida.
Com essa navegação em Drawer, utilizando um menu que fica oculto na tela, economizamos espaço e mantemos a interface principal limpa, deixando funcionalidades menos utilizadas acessíveis, mas não invasivas.
Por isso, ela é uma solução ideal para aplicações que possuem muitas opções de navegação, mas precisam de uma interface limpa e intuitiva.
Já conhecemos mais sobre o Drawer e como ele funciona, agora vem comigo analisar o exemplo que deixamos neste Gist do GitHub.
Ao analisar o código no arquivo _layout.tsx
, notamos que além de usar a tag <Drawer/>
, precisamos envolver ela com a tag <Gesture Handler RootView>
para habilitar gestos.
Contudo, isso precisa ser feito apenas no arquivo _ layout.tsx
, nenhuma outra rota aninhada precisa dessa implementação de forma individual.
E da mesma forma como na navegação por Tabs, conseguimos personalizar ícones e títulos.
Para isso,usamos a tag <Drawer.Screen />
que possui as propriedades name
que indica qual é o arquivo que deve ser aberto na aplicação, e as options
que permitem alterar o título da opção com o drawerLabel
e inserir um ícone com o drawerIcon
.
Com isso, o resultado é o mostrado abaixo:

Conclusão
Estamos chegando ao final desse artigo, e ao longo do texto, conhecemos muitas maneiras de navegar entre telas em um app com a ajuda do expo-router
.
Conhecemos a navegação por Stacks, que empilha as telas, com Tabs, que fixa abas na parte inferior da página e com Drawer que cria um menu oculto.
Entendemos como funciona o ciclo de vida e a performance de cada abordagem e analisamos exemplos em código para cada situação.
E a pergunta que fica é: qual a melhor abordagem de navegação? A resposta é: depende!
Todas podem ser usadas individualmente ou combinadas para gerar resultados incríveis que melhorem a experiência de quem usar o app.
O importante é entender como a aplicação funciona e testar diferentes formas de transitar entre telas até encontrar a que melhor atende às necessidades da aplicação.
Caso queira aprimorar ainda mais seus conhecimentos em React Native, vem conferir a nova formação de React Native: Desenvolva seu primeiro app que vai te ajudar a começar no desenvolvimento mobile.
Um grande abraço e até a próxima!