Kurs React Native · Moduł 3: Nawigacja

Expo Router - Nowoczesna Nawigacja

4 min czytania ·
W tej lekcji10

Expo Router to nowy, rewolucyjny system nawigacji oparty na strukturze plików - podobnie jak Next.js dla React Native!

Dlaczego Expo Router?

  • File-based routing - struktura folderów = struktura nawigacji
  • Automatyczny Deep Linking - bez dodatkowej konfiguracji
  • Typed routes - pełne wsparcie TypeScript
  • Web support - działa na web jak Next.js
  • Lazy loading - ekrany ładowane na żądanie

Instalacja

# Nowy projekt z Expo Router
npx create-expo-app@latest --template tabs

# Lub dodanie do istniejącego projektu
npx expo install expo-router expo-linking expo-constants

Konfiguracja

// app.json
{
  "expo": {
    "scheme": "atlantyda",
    "web": {
      "bundler": "metro"
    },
    "plugins": ["expo-router"]
  }
}
// package.json
{
  "main": "expo-router/entry"
}

Struktura plików = Routing

app/
├── _layout.tsx          # Layout główny
├── index.tsx            # / (strona główna)
├── about.tsx            # /about
├── settings/
│   ├── _layout.tsx      # Layout dla /settings/*
│   ├── index.tsx        # /settings
│   └── profile.tsx      # /settings/profile
├── user/
│   └── [id].tsx         # /user/123 (dynamiczny parametr)
├── (tabs)/
│   ├── _layout.tsx      # Tab Navigator
│   ├── home.tsx         # Zakładka Home
│   └── search.tsx       # Zakładka Search
└── +not-found.tsx       # Strona 404

Root Layout

// app/_layout.tsx
import { Stack } from 'expo-router';
import { ThemeProvider } from '@/providers/ThemeProvider';

export default function RootLayout() {
  return (
    <ThemeProvider>
      <Stack
        screenOptions={{
          headerStyle: { backgroundColor: '#0a2e4a' },
          headerTintColor: '#fff',
        }}
      >
        <Stack.Screen name="index" options={{ title: 'Atlantyda' }} />
        <Stack.Screen name="(tabs)" options={{ headerShown: false }} />
        <Stack.Screen name="user/[id]" options={{ title: 'Profil' }} />
      </Stack>
    </ThemeProvider>
  );
}

Strona główna

// app/index.tsx
import { View, Text, StyleSheet } from 'react-native';
import { Link } from 'expo-router';

export default function Home() {
  return (
    <View style={styles.container}>
      <Text style={styles.title}>Witaj w Atlantydzie!</Text>

      {/* Link jako komponent */}
      <Link href="/about" style={styles.link}>
        O aplikacji
      </Link>

      {/* Link z parametrami */}
      <Link
        href={{
          pathname: '/user/[id]',
          params: { id: '123' }
        }}
        style={styles.link}
      >
        Mój profil
      </Link>

      {/* Link do Tab Navigator */}
      <Link href="/(tabs)/home" style={styles.link}>
        Przejdź do zakładek
      </Link>
    </View>
  );
}

Dynamiczne trasy

// app/user/[id].tsx
import { useLocalSearchParams, useGlobalSearchParams, Stack } from 'expo-router';

export default function UserProfile() {
  // Pobierz parametr z URL
  const { id } = useLocalSearchParams<{ id: string }>();

  return (
    <View>
      {/* Dynamiczny tytuł */}
      <Stack.Screen options={{ title: `Użytkownik #${id}` }} />

      <Text>Profil użytkownika: {id}</Text>
    </View>
  );
}

Tab Navigator

// app/(tabs)/_layout.tsx
import { Tabs } from 'expo-router';
import { Ionicons } from '@expo/vector-icons';

export default function TabLayout() {
  return (
    <Tabs
      screenOptions={{
        tabBarActiveTintColor: '#00d4ff',
        tabBarStyle: { backgroundColor: '#0a2e4a' },
      }}
    >
      <Tabs.Screen
        name="home"
        options={{
          title: 'Główna',
          tabBarIcon: ({ color, size }) => (
            <Ionicons name="home" size={size} color={color} />
          ),
        }}
      />
      <Tabs.Screen
        name="search"
        options={{
          title: 'Szukaj',
          tabBarIcon: ({ color, size }) => (
            <Ionicons name="search" size={size} color={color} />
          ),
        }}
      />
    </Tabs>
  );
}

Nawigacja programowa

import { router } from 'expo-router';

function NavigationButtons() {
  return (
    <View>
      {/* Nawigacja do ścieżki */}
      <Pressable onPress={() => router.push('/about')}>
        <Text>O aplikacji</Text>
      </Pressable>

      {/* Nawigacja z parametrami */}
      <Pressable onPress={() => router.push({
        pathname: '/user/[id]',
        params: { id: '456' }
      })}>
        <Text>Profil 456</Text>
      </Pressable>

      {/* Replace (bez możliwości powrotu) */}
      <Pressable onPress={() => router.replace('/login')}>
        <Text>Wyloguj</Text>
      </Pressable>

      {/* Powrót */}
      <Pressable onPress={() => router.back()}>
        <Text>Wróć</Text>
      </Pressable>

      {/* Sprawdzenie czy można wrócić */}
      {router.canGoBack() && (
        <Pressable onPress={() => router.back()}>
          <Text>Wróć</Text>
        </Pressable>
      )}
    </View>
  );
}

Grupy (ukrywanie z URL)

// Grupy w nawiasach nie pojawiają się w URL
// app/(auth)/login.tsx → /login
// app/(tabs)/home.tsx → /home (nie /(tabs)/home)

// app/(auth)/_layout.tsx
import { Stack } from 'expo-router';

export default function AuthLayout() {
  return (
    <Stack screenOptions={{ headerShown: false }}>
      <Stack.Screen name="login" />
      <Stack.Screen name="register" />
    </Stack>
  );
}

Expo Router to przyszłość nawigacji w React Native. Znacznie upraszcza routing i automatycznie obsługuje Deep Linking!

Kod do tej lekcji: App.tsx
// Plik: app/_layout.tsx (Expo Router - file-based routing)
import React, { useState } from 'react';
import { Stack, Redirect } from 'expo-router';

// Prosty hook symulujący stan zalogowania użytkownika
function useAuth() {
  const [isSignedIn] = useState(false);
  return { isSignedIn };
}

export default function RootLayout() {
  const { isSignedIn } = useAuth();

  // TODO: jeśli użytkownik NIE jest zalogowany, przekieruj do ekranu logowania:
  // if (!isSignedIn) {
  //   return <Redirect href="/login" />;
  // }

  // TODO: zwróć <Stack>, w którym ukryjesz nagłówek dla grupy (tabs):
  // <Stack>
  //   <Stack.Screen name="(tabs)" options={{ headerShown: false }} />
  // </Stack>

  return null;
}

Hasła z dokumentacji w tej lekcji

Widzisz błąd w tej lekcji?

Sprawdź się

Odpowiedz na pytania z tej lekcji. Wybierz odpowiedź, a od razu zobaczysz, czy jest poprawna.

  1. 1. Na czym polega file-based routing w Expo Router?

  2. 2. Co oznacza nazwa pliku [id].tsx w Expo Router?

To 2 z 4 pytań do tej lekcji. Pozostałe rozwiążesz w grze.

Zadania praktyczne w grze

  • Układanie w pionie

    Uporządkuj zagnieżdżenie layoutów od głównego:

  • Edytor kodu

    Uzupełnij kod _layout.tsx.

  • Układanie w poziomie

    Ułóż pobieranie parametrów w Expo Router:

Przydatne artykuły