React Navigation 實作多頁面路由
在網頁開發中,我們習慣透過改變網址列的 URL (例如 /home 或 /profile) 來切換頁面。但在手機 App 中,沒有網址列這種東西。
我們必須依賴導覽庫 (Navigation Library) 來管理畫面層級。在 React Native 生態系中,最權威、市佔率最高的解決方案就是 React Navigation,而最新的 Expo Router 更是把它做成了跟 Next.js 幾乎一模一樣的「檔案系統路由 (File-based Routing)」!
1. 什麼是 Expo Router?
如果你過去有寫過舊版的 RN,你一定記得要寫一堆 Stack.Navigator 跟 Stack.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 定位與發送推播通知!