React Navigation 實作多頁面路由

在網頁開發中,我們習慣透過改變網址列的 URL (例如 /home/profile) 來切換頁面。但在手機 App 中,沒有網址列這種東西

我們必須依賴導覽庫 (Navigation Library) 來管理畫面層級。在 React Native 生態系中,最權威、市佔率最高的解決方案就是 React Navigation,而最新的 Expo Router 更是把它做成了跟 Next.js 幾乎一模一樣的「檔案系統路由 (File-based Routing)」!

1. 什麼是 Expo Router?

如果你過去有寫過舊版的 RN,你一定記得要寫一堆 Stack.NavigatorStack.Screen 的恐怖經驗。

在最新版的 Expo 中,他們引入了 Expo Router。它直接借鏡了 Next.js 的 App Router 概念:你在資料夾裡開什麼檔案,那個檔案就會自動變成一個頁面!

你的資料夾結構看起來會像這樣:

app/
 ├── _layout.tsx    # 全域佈局設定
 ├── index.tsx      # 首頁 (預設開啟)
 ├── profile.tsx    # 個人檔案頁面
 └── settings.tsx   # 設定頁面

2. Stack Navigation:堆疊式導覽

手機 App 最常見的導覽方式就是「堆疊 (Stack)」。 想像一疊撲克牌,當你點擊一個商品進入詳細頁時,就像是放了一張新牌在最上面。當你點擊左上角的 < 返回 時,就是把最上面的牌抽走,回到上一頁。

app/_layout.tsx 中定義一個 Stack 佈局:

import { Stack } from 'expo-router';

export default function RootLayout() {
  return (
    <Stack>
      {/* 針對不同的頁面客製化頂部標題列 (Header) */}
      <Stack.Screen 
        name="index" 
        options={{ title: '首頁', headerShown: false }} 
      />
      <Stack.Screen 
        name="profile" 
        options={{ title: '個人檔案', presentation: 'modal' }} 
      />
      <Stack.Screen 
        name="settings" 
        options={{ title: '系統設定' }} 
      />
    </Stack>
  );
}

[!TIP] 注意 presentation: 'modal' 這個選項。在 iOS 上,加上這個設定會讓新頁面從「由下往上」滑出(而不是預設的由右往左),這就是經典的原生互動體驗!

3. 在頁面之間跳轉 (Link 與 router)

有了頁面後,我們該怎麼跳轉呢?跟 Next.js 非常像,我們有兩種方式:

方式一:使用 <Link> 元件 (適合按鈕/文字)

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

export default function Home() {
  return (
    <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
      <Text>歡迎來到首頁</Text>
      
      {/* asChild 代表把點擊事件綁定在內部的 TouchableOpacity 上 */}
      <Link href="/profile" asChild>
        <TouchableOpacity style={{ marginTop: 20, padding: 10, backgroundColor: 'blue' }}>
          <Text style={{ color: 'white' }}>前往個人檔案</Text>
        </TouchableOpacity>
      </Link>
    </View>
  );
}

方式二:使用 router API (適合在函式邏輯中)

import { router } from 'expo-router';

function handleLoginSuccess() {
  // 驗證成功後,用程式碼強制跳轉
  router.push('/dashboard');
  
  // 或者如果你不希望使用者按返回鍵退回登入頁,可以使用 replace
  // router.replace('/dashboard');
}

4. 實戰:打造 IG 風格的底部導覽列 (Bottom Tabs)

現在幾乎所有主流 App(如 Instagram, Line, Spotify)都是使用底部導覽列。 在 Expo Router 實作這個功能超級簡單!

首先,我們要建立一個專門給 Tabs 用的資料夾結構:

app/
 ├── _layout.tsx         # 根目錄的 Stack (用來放登入頁等沒有 Tab 的畫面)
 └── (tabs)/             # 括號代表這是一個路由群組,不會顯示在 URL 中
      ├── _layout.tsx    # 定義 Bottom Tabs 的長相
      ├── index.tsx      # 首頁 Tab
      └── explore.tsx    # 探索 Tab

我們來設定 app/(tabs)/_layout.tsx

import { Tabs } from 'expo-router';
import { Home, Search } from 'lucide-react-native'; // 記得先安裝 icon 套件

export default function TabLayout() {
  return (
    <Tabs screenOptions={{ 
      tabBarActiveTintColor: '#3b82f6', // 選中時的顏色
      headerShown: true // 是否顯示頂部標題列
    }}>
      <Tabs.Screen
        name="index"
        options={{
          title: '首頁',
          tabBarIcon: ({ color }) => <Home color={color} size={24} />,
        }}
      />
      <Tabs.Screen
        name="explore"
        options={{
          title: '探索',
          tabBarIcon: ({ color }) => <Search color={color} size={24} />,
        }}
      />
    </Tabs>
  );
}

就這樣短短幾行 Code!一個完美符合 iOS / Android 原生設計規範、帶有滑動動畫與觸覺回饋的底部導覽列就誕生了!

5. 傳遞參數 (Route Parameters)

如果我們在「文章列表」點擊了一篇文章,要跳轉到「文章詳細頁」,我們必須把文章的 ID 傳過去。

發送參數:

// 在列表頁
router.push({ pathname: '/post/[id]', params: { id: 123, title: '你好' } });

接收參數: 如果你的檔案叫做 app/post/[id].tsx

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

export default function PostDetail() {
  // 解構出剛剛傳過來的參數
  const { id, title } = useLocalSearchParams();

  return (
    <View>
      <Text>目前正在觀看文章 ID: {id}</Text>
      <Text>文章標題: {title}</Text>
    </View>
  );
}

掌握了導覽系統,你的 App 就具備了完整的骨架。下一章,我們將進入只有手機 App 才能做到的領域:呼叫相機、取得 GPS 定位與發送推播通知!

解鎖完整教學內容

本章為付費內容。加入專案即可解鎖超過 5000 字的深度解析,包含 10 個以上神級 Prompt 與真實 Source Code 範例!