Kurs React Native · Moduł 3: Nawigacja
Expo Router - Nowoczesna Nawigacja
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-constantsKonfiguracja
// 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 404Root 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. Na czym polega file-based routing w Expo Router?
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: