React Navigation ou Expo Router: qual escolher para o react native

React Navigation ou Expo Router: qual escolher para o react native

Um app, em boa parte das vezes, não é composto só por uma tela, mas sim, diferentes telas que são compostas por diversos componentes. Por isso a importância da navegação, para que se tenha uma conexão entre essas telas.

No ecossistema do React Native, duas das opções mais populares para navegação são o React Navigation e o Expo Router.

Neste artigo, vamos explorar as diferenças dessas duas tecnologias e ajudar você a fazer a melhor escolha para o seu projeto.

O que é o React Navigation?

Se você já aprendeu React.js para web, provavelmente já deve ter visto o React Router.

O React Navigation tem uma proposta parecida, mas adaptada para o ambiente mobile, com as mesmas facilidades de navegação, porém com recursos extras, como o controle de gestos e animações específicas para apps móveis.

O React Navigation é uma das bibliotecas mais populares para navegação em React Native e pode ser utilizado tanto em projetos React Native puros quanto em projetos baseados no Expo.

Ah, você prestou atenção nesse último ponto? Sim, o React Navigation é compatível com projetos Expo também, e isso não é só uma coincidência.

Na verdade, isso se deve principalmente ao fato de o Expo Router (que vamos explorar daqui a pouco) utilizar como base para sua construção o próprio React Navigation.

É isso aí dev, nada acontece por acaso. Mas agora, que tal analisarmos o React Navigation na prática?

Implementação com React Navigation

Vamos criar um aplicativo que terá duas telas: uma tela inicial que exibe uma lista de pessoas usuárias e uma tela de detalhes que apresenta informações sobre a pessoa selecionada.

Nós iremos utilizar o Expo para inicializar o projeto execute o comando:

npx create-expo-app@latest MeuApp

Após criar o projeto, entre na pasta do projeto e digite o comando:

npm run reset-project

Caso apareça essa pergunta: "Do you want to move existing files to /app-example instead of deleting them? (Y/n)", responda com "n", isso fará com que todo o template inicial seja descartado.

Também será necessário instalar as dependências necessárias para a navegação. Para isso, execute:

npm install @react-navigation/native @react-navigation/native-stack

Em seguida, instale também o resto das dependências necessárias com expo:

npx expo install react-native-screens react-native-safe-area-context

Recomendo que você renomeie os arquivos index e layout para o formato .jsx, porque nesse projeto não iremos utilizar Typescript, também iremos fazer a criação de uma pasta de screens que será responsável por armazenar as telas do nosso app.

Nosso app terá a seguinte estrutura:

MeuApp/
├── app/
│   ├── _layout.jsx
│   ├── index.jsx
│   └── screens/
│       ├── details_screen.jsx
│       ├── home_screen.jsx

Para configurar a navegação no React Navigation, dentro do arquivo _layout.jsx, é necessário importar os componentes e definir a estrutura de navegação utilizando um Stack.Navigator.

Como no nosso app teremos apenas uma tela de Home e Details, o nosso _layout.jsx irá ficar da seguinte forma:

import * as React from 'react';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import HomeScreen from './screens/home_screen';
import DetailsScreen from './screens/details_screen';

const Stack = createNativeStackNavigator();

export default function RootLayout() {
  return (
    <Stack.Navigator>
      <Stack.Screen name="Home" component={HomeScreen} />
      <Stack.Screen name="Details" component={DetailsScreen} />
    </Stack.Navigator>
  );
}

Esse código define a estrutura de navegação do aplicativo, permitindo transitar entre as telas Home e Details.

Nosso index.jsx ficará da seguinte maneira:

import * as React from 'react';
import { NavigationContainer } from '@react-navigation/native';
import RootLayout from './_layout';

export default function Index() {
  return (
    <NavigationContainer>
      <RootLayout />
    </NavigationContainer>
  );
}

Agora, podemos criar de fato a tela de home e de details. a HomeScreen irá conter um botão que redireciona a pessoa usuária para a DetailsScreen, passando o userId e o email ao navegar para DetailsScreen:

import React from 'react';
import { View, Text, TouchableOpacity, StyleSheet } from 'react-native';

export default function HomeScreen({ navigation }) {
  const users = [
    { id: 1, name: 'Alice', email: '[email protected]' },
    { id: 2, name: 'Bob', email: '[email protected]' },
    { id: 3, name: 'Charlie', email: '[email protected]' },
    { id: 4, name: 'David', email: '[email protected]' },
  ];

  return (
    <View style={styles.container}>
      {users.map((user) => (
        <TouchableOpacity
          key={user.id}
          style={styles.button}
          onPress={() => navigation.navigate('Details', { userId: user.id, email: user.email })}
        >
          <Text style={styles.buttonText}>Ver detalhes de {user.name}</Text>
        </TouchableOpacity>
      ))}
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: '#F8F9FA',
    padding: 20,
  },
  button: {
    backgroundColor: '#007BFF',
    width: '80%',
    paddingVertical: 12,
    borderRadius: 8,
    alignItems: 'center',
    marginBottom: 12, 
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 2 },
    shadowOpacity: 0.2,
    shadowRadius: 3,
    elevation: 3,
  },
  buttonText: {
    color: '#FFF',
    fontSize: 16,
    fontWeight: 'bold',
  },
});

Neste código, com o array users e utilizando o método .map estamos renderizando de forma dinâmica uma lista de pessoas usuárias

Na DetailsScreen, é possível acessar o userId e o email através de route.params e exibir a informação na interface.

import React from 'react';
import { View, Text, StyleSheet } from 'react-native';

export default function DetailsScreen({ route }) {
  const { userId, email } = route.params;

  return (
    <View style={styles.container}>
      <Text style={styles.title}>Detalhes da pessoa</Text>
      <Text style={styles.info}>ID: {userId}</Text>
      <Text style={styles.info}>Email: {email}</Text>
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: '#FFF',
    padding: 20,
  },
  title: {
    fontSize: 22,
    fontWeight: 'bold',
    marginBottom: 12,
    color: '#333',
  },
  info: {
    fontSize: 18,
    marginBottom: 8,
    color: '#555',
  },
});

Com o app já criado, para inicializá-lo, utilize o :

npx expo start

Lembre-se que caso queira abrir em um dispositivo android deve utilizar um dispositivo físico ou ter um emulador instalado e executando.

Pronto, conseguimos inicializar um projeto com React Native e utilizar o React Navigation nele.

Agora, chegou o momento de conhecermos o Expo Router!

Banner da Alura apresentando a Imersão Mobile, uma oportunidade para aprender Flutter criando um app de delivery na prática. Participe de 3 aulas gratuitas, desenvolva um projeto para portfólio e conquiste seu certificado!

O que é o Expo Router?

O Expo Router é uma das maneiras mais utilizadas para implementar a navegação em apps desenvolvidos no ambiente Expo.

Ele se baseia no conceito de navegação por arquivos, o que torna a estrutura do código mais intuitiva.

Sua principal vantagem está na configuração automática dentro do Expo, reduzindo a necessidade de ajustes manuais e simplificando a implementação.

Além disso, sua abordagem baseada em URLs facilita a integração com deep linking e torna a passagem de parâmetros mais direta.

Para quem está começando no desenvolvimento de aplicativos, essa pode ser uma excelente opção por ser mais simples e rápida de configurar.

Agora usando o Expo Router, vamos implementar o mesmo app que fizemos utilizando o React Navigation.

Implementação com Expo Router

Novamente, iremos utilizar o Expo, para inicializar o projeto execute o comando:

npx create-expo-app@latest MeuApp

Após criar o projeto, entre na pasta do projeto e digite o comando:

npm run reset-project

Recomendo que você renomeie os arquivos index e layout para o formato .jsx, porque nesse projeto não iremos utilizar Typescript, também faremos a criação de uma pasta de screens que será responsável por armazenar as telas do nosso app.

Aqui no Expo Router criamos apenas a tela de detalhes e usar o index como home, mas fique à vontade para criar uma outra tela.

Nosso app ficará com a seguinte estrutura de pastas:

MeuApp/
├── app/
│   ├── _layout.jsx
│   ├── index.jsx
│   └── screens/
│       ├── details_screen.jsx

O arquivo _layout.jsx é responsável por definir o layout e a navegação do aplicativo. Ele organiza a estrutura da página, como cabeçalhos, menus e rodapés, e gerencia como a pessoa usuária navega entre as telas.

Fazendo com que o visual e a navegação sejam sempre os mesmos, sem precisar configurar tudo em cada página, além de facilitar a manutenção do app, pois qualquer mudança no design ou na navegação pode ser feita de uma vez, de forma centralizada.

No nosso app, o arquivo _layout.jsx irá ficar da seguinte maneira:

import { Stack } from 'expo-router';

export default function RootLayout() {
  return (
    <Stack>
      <Stack.Screen name="index" options={{ title: "Home" }} />
      <Stack.Screen name="screens/details_screen" options={{ title: "Detalhes" }} />
    </Stack>
  );
}

Na tela inicial (index.js), é possível criar links para as telas de detalhes. Diferente do React Navigation, aqui utilizamos o componente Link do Expo Router para navegar entre as telas, funcionando de maneira semelhante a links em uma página da web. Agora, vamos criar a tela de detalhes.

import { Link } from "expo-router";
import { StyleSheet, Text, TouchableOpacity, View } from "react-native";

export default function Index() {
  const users = [
    { id: 1, name: 'Alice', email: '[email protected]' },
    { id: 2, name: 'Bob', email: '[email protected]' },
    { id: 3, name: 'Charlie', email: '[email protected]' },
    { id: 4, name: 'David', email: '[email protected]' },
  ];

  return (
    <View style={styles.container}>
      {users.map((user) => (
        <Link
          key={user.id}
          href={`/screens/details_screen?id=${user.id}&email=${user.email}`}
          asChild
        >
          <TouchableOpacity style={styles.button}>
            <Text style={styles.buttonText}>Ver detalhes de {user.name}</Text>
          </TouchableOpacity>
        </Link>
      ))}
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: '#F8F9FA',
    padding: 20,
  },
  button: {
    backgroundColor: '#007BFF',
    width: '80%',
    paddingVertical: 12,
    borderRadius: 8,
    alignItems: 'center',
    marginBottom: 12,
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 2 },
    shadowOpacity: 0.2,
    shadowRadius: 3,
    elevation: 3,
  },
  buttonText: {
    color: '#FFF',
    fontSize: 16,
    fontWeight: 'bold',
  },
});

A tela details_screen.jsx recebe o ID e o email como parâmetros utilizando useLocalSearchParams e exibe as informações correspondentes.

import { View, Text, StyleSheet } from 'react-native';
import { useLocalSearchParams } from 'expo-router';

export default function DetailsScreen() {
  const { id, email } = useLocalSearchParams();

  return (
    <View style={styles.container}>
      <Text style={styles.title}>Detalhes</Text>
      <Text style={styles.info}>ID: {id}</Text>
      <Text style={styles.info}>Email: {email}</Text>
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: '#FFF',
    padding: 20,
  },
  title: {
    fontSize: 22,
    fontWeight: 'bold',
    marginBottom: 12,
    color: '#333',
  },
  info: {
    fontSize: 18,
    marginBottom: 8,
    color: '#555',
  },
});

Gostou do resultado? O Expo facilita tudo, não é mesmo?

Caso queira abrir em um dispositivo android deve utilizar um dispositivo físico ou ter um emulador instalado e executando.

Para abrir seu projeto, utilize o:

npx expo start

Este será o resultado final do nosso app:

Tela de um aplicativo móvel exibindo a página "Home" com quatro botões azuis centralizados, cada um contendo um texto para visualizar detalhes de diferentes pessoas: "Ver detalhes de Alice", "Ver detalhes de Bob", "Ver detalhes de Charlie" e "Ver detalhes de David". Ao clicar em um botão é feito o redirecionamento para telas de detalhes que mostra o id e e-mail da pessoa selecionada

Neste momento, depois de utilizarmos tanto o React Navigation quanto o Expo Router, surge uma grande dúvida: qual escolher?

React Navigation ou Expo Router?

Para resolver de vez essa dúvida "cruel", vamos analisar o exemplo a seguir:

import { View, Text, Button, Animated } from 'react-native';
import { useRouter } from 'expo-router';
import { useEffect, useRef } from 'react';

export default function Home() {
  const router = useRouter(); 
  const fadeAnim = useRef(new Animated.Value(0)).current; //opacidade

  useEffect(() => {
    Animated.timing(fadeAnim, {
      toValue: 1,
      duration: 300,
      useNativeDriver: true,
    }).start();
  }, []);

  return (
    <Animated.View style={{ flex: 1, justifyContent: 'center', alignItems: 'center', opacity: fadeAnim }}>
      <Text>Home Screen</Text>
      <Button
        title="Ir para perfil"
        onPress={() => {
          alert('Navegação de exemplo');
        }}
      />
    </Animated.View>
  );
}

Adicionamos uma animação de fade-in para mostrar que o Expo também suporta animações.

Porém, as transições são mais limitadas, com poucas opções de personalização e, dependendo do que você precisa para o seu app, o Expo Router pode não oferecer toda a flexibilidade necessária.

Já no React Navigation é diferente, ele permite ter quase total controle na criação de animações, como no exemplo a seguir:

import { View, Text, Button, Animated } from 'react-native';
import { useNavigation } from '@react-navigation/native';
import { useEffect, useRef } from 'react';

export default function HomeScreen() {
  const navigation = useNavigation();
  const fadeAnim = useRef(new Animated.Value(0)).current; // Opacidade
  const scaleAnim = useRef(new Animated.Value(0.8)).current; // Escala

  useEffect(() => {
    // Animação de fade-in e escala
    Animated.sequence([
      Animated.timing(fadeAnim, {
        toValue: 1,
        duration: 500,
        useNativeDriver: true,
      }),
      Animated.timing(scaleAnim, {
        toValue: 1,
        duration: 500,
        useNativeDriver: true,
      }),
    ]).start();
  }, []);

  return (
    <Animated.View
      style={{
        flex: 1,
        justifyContent: 'center',
        alignItems: 'center',
        opacity: fadeAnim,
        transform: [{ scale: scaleAnim }],
      }}
    >
      <Text>Home Screen</Text>
      <Button
        title="Ir para o Perfil"
        onPress={() => {
          alert('Navegação de exemplo');
        }}
      />
    </Animated.View>
  );
}

Neste código, criamos uma tela inicial (HomeScreen) com animações de fade-in e escala utilizando o Animated do React Native. Ao carregar a tela, a opacidade passa de 0 a 1 e a escala de 0.8 a 1, criando uma transição suave.

Viu só? Com React Navigation, podemos, de fato, montar uma animação exatamente como quisermos, como se fosse um quebra-cabeça.

Claro, aqui exploramos principalmente as animações, mas esse ganho em personalização do React Navigation está presente também em outras etapas que compõem a estrutura de navegação.

"Ah, já sei, devo sempre usar React Navigation!"

Calma! Aqui mostramos algumas limitações do Expo Router, mas também vimos o quanto ele simplifica a configuração da navegação.

No final, a decisão vai depender do contexto e das necessidades do seu projeto! Se está começando agora e quer evitar configurações complexas, o Expo Router pode ser um ótimo ponto de partida. Se precisar de mais controle sobre animações e outros aspectos que compõem a navegação do seu app, o React Navigation será mais adequado.

Conclusão

Este artigo tem como principal referência as documentações de cada uma dessas tecnologias:

Independente da sua escolha, a Alura está aqui para te ajudar a dar o próximo passo, confira nossa formação de React Native:

Um abraço, dev! Até a próxima!

Mikael Diniz
Mikael Diniz

Atualmente estou cursando Ciência da Computação na UFMA, sou apaixonado por programação, games, matemática, basquete, programação competitiva e LeetCode.

Veja outros artigos sobre Mobile