# Expo Router สำหรับ React Native: คู่มือระบบ Navigation แบบ File-Based ฉบับสมบูรณ์
> คู่มือฉบับสมบูรณ์สำหรับ Expo Router ใน React Native ครอบคลุมระบบ routing แบบ file-based, การตั้งค่า layout, tab navigation, dynamic routes, modal screens, typed routes, middleware และการป้องกันเส้นทาง พร้อมตัวอย่างโค้ดจริง
- Published: 2026-04-18
- Updated: 2026-04-18
- Author: SharpSkill
- Tags: react-native, expo, expo-router, navigation, mobile-development, tutorial
- Reading time: 9 min
---
Expo Router เปลี่ยนแปลงวิธีจัดการระบบ navigation ในแอปพลิเคชัน React Native อย่างสิ้นเชิง ด้วยการนำแนวคิด file-based routing ที่คุ้นเคยจาก Next.js มาปรับใช้กับโลกของ mobile development ตั้งแต่ SDK Expo 55 และ Expo Router v6 เป็นต้นมา นักพัฒนาไม่จำเป็นต้องตั้งค่า navigator ด้วยตนเองอีกต่อไป เพียงสร้างไฟล์ในไดเรกทอรี `app` หน้าจอนั้นก็พร้อมใช้งานทันทีทั้งบน Android, iOS และเว็บ แนวทางนี้ลดความซับซ้อนของโปรเจกต์ข้ามแพลตฟอร์มลงอย่างมาก และเร่งกระบวนการพัฒนาให้รวดเร็วยิ่งขึ้น
> **เริ่มต้นอย่างรวดเร็ว**
>
> โปรเจกต์ Expo ใหม่ทุกโปรเจกต์มี Expo Router ติดตั้งมาให้พร้อมใช้งาน คำสั่ง `npx create-expo-app@latest --template default@sdk-55` จะสร้างโปรเจกต์ที่ตั้งค่า file-based routing ไว้เรียบร้อยแล้ว สำหรับโปรเจกต์ที่มีอยู่เดิม เพียงติดตั้งแพ็กเกจ `expo-router` และอัปเดต entry point ของแอปพลิเคชัน
## หลักการทำงานของ File-Based Routing ใน Expo Router
ไฟล์ทุกไฟล์ที่สร้างขึ้นภายในไดเรกทอรี `app` จะกลายเป็น route โดยอัตโนมัติ เส้นทางของไฟล์จะสอดคล้องกับเส้นทาง URL โดยตรง ทำให้ไม่จำเป็นต้องมีไฟล์ configuration แบบรวมศูนย์อีกต่อไป ตัวอย่างเช่น ไฟล์ `app/settings.tsx` จะสร้าง route `/settings` ในขณะที่ `app/profile/edit.tsx` จะตรงกับ `/profile/edit`
แนวทางนี้มีข้อได้เปรียบหลักสามประการเมื่อเทียบกับการตั้งค่า React Navigation แบบดั้งเดิม:
- **ไม่ต้องตั้งค่าใดๆ** -- route ถูกสร้างขึ้นทันทีที่สร้างไฟล์
- **Deep linking อัตโนมัติ** -- ทุกหน้าจอมี URL เป็นของตัวเอง ทำให้การแชร์ลิงก์และการทดสอบสะดวกยิ่งขึ้น
- **Navigation ที่มี type safety** -- TypeScript รู้จัก route ทั้งหมดที่มีอยู่ตั้งแต่ขั้นตอน compilation
```typescript
// app/index.tsx
import { View, Text, StyleSheet } from 'react-native'
import { Link } from 'expo-router'
export default function HomeScreen() {
return (
Welcome
{/* Link maps directly to file path */}
Open Settings
Edit Profile
)
}
const styles = StyleSheet.create({
container: { flex: 1, justifyContent: 'center', padding: 24 },
title: { fontSize: 28, fontWeight: 'bold', marginBottom: 16 },
link: { fontSize: 16, color: '#61DAFB', marginTop: 12 },
})
```
คอมโพเนนต์ `Link` จัดการ navigation บนทุกแพลตฟอร์ม บนเว็บจะสร้าง anchor tag พร้อม attribute `href` ที่เหมาะสมสำหรับ SEO ส่วนบนแพลตฟอร์ม native จะทำงานเป็น stack navigation ตามปกติ
## โครงสร้างโปรเจกต์และไฟล์ Layout
Expo Router ใช้ไฟล์ `_layout.tsx` สำหรับกำหนด navigation container แต่ละไดเรกทอรีสามารถมี layout เป็นของตัวเอง ทำให้เกิดโครงสร้าง navigation แบบซ้อนกันได้ Layout หลักที่อยู่ระดับ root จะครอบคลุมทั้งแอปพลิเคชัน ส่วน layout ที่ซ้อนอยู่ภายในจะควบคุมเฉพาะส่วนที่เกี่ยวข้อง
โครงสร้างโปรเจกต์ตัวอย่างมีลักษณะดังนี้:
```text
app/
_layout.tsx # Root layout (Stack or custom)
index.tsx # Home screen (/)
(tabs)/ # Tab group (parentheses = route group)
_layout.tsx # Tab navigator
home.tsx # /home tab
search.tsx # /search tab
profile.tsx # /profile tab
settings/
_layout.tsx # Settings stack layout
index.tsx # /settings
notifications.tsx # /settings/notifications
privacy.tsx # /settings/privacy
```
Route groups คือไดเรกทอรีที่ล้อมรอบด้วยวงเล็บ ซึ่งทำหน้าที่จัดระเบียบไฟล์โดยไม่ส่งผลกระทบต่อ URL ไดเรกทอรี `(tabs)` ในตัวอย่างด้านบนสร้าง tab navigator ขึ้นมา แต่ URL ยังคงเป็น `/home`, `/search` และ `/profile` ไม่ใช่ `/tabs/home`
```typescript
// app/_layout.tsx
import { Stack } from 'expo-router'
export default function RootLayout() {
return (
)
}
```
Layout หลักยังทำหน้าที่เป็นจุดเริ่มต้นสำหรับการโหลดฟอนต์ การตั้งค่า provider ต่างๆ และการกำหนดค่าระดับ global อีกด้วย โดยมาแทนที่ไฟล์ `App.tsx` แบบดั้งเดิมในฐานะ entry point ของแอปพลิเคชัน
## การสร้าง Tab Navigation
Tab navigation ต้องมีไฟล์ `_layout.tsx` อยู่ภายใน route group Expo Router v6 มีคอมโพเนนต์ `NativeTabs` สำหรับประสบการณ์ native เฉพาะแพลตฟอร์ม แต่คอมโพเนนต์ `Tabs` มาตรฐานก็ครอบคลุมกรณีการใช้งานส่วนใหญ่ได้เป็นอย่างดี
```typescript
// app/(tabs)/_layout.tsx
import { Tabs } from 'expo-router'
import { Ionicons } from '@expo/vector-icons'
export default function TabLayout() {
return (
(
),
}}
/>
(
),
}}
/>
(
),
}}
/>
)
}
```
ไฟล์หน้าจอของแต่ละ tab จะ export คอมโพเนนต์ React มาตรฐาน ไอคอน, ข้อความ และ badge ของ tab bar สามารถกำหนดค่าได้ผ่าน prop `options` ใน layout
## Dynamic Routes และ Route Parameters
Segment แบบ dynamic ใช้วงเล็บเหลี่ยมในชื่อไฟล์ ไฟล์ที่ตั้งชื่อว่า `[id].tsx` จะจับคู่กับ segment เดียว ในขณะที่ `[...slug].tsx` จะจับคู่กับ segment ที่เหลือทั้งหมดในเส้นทาง
```typescript
// app/product/[id].tsx
import { View, Text, StyleSheet } from 'react-native'
import { useLocalSearchParams, Stack } from 'expo-router'
export default function ProductScreen() {
// Extract the dynamic parameter from the URL
const { id } = useLocalSearchParams<{ id: string }>()
return (
Product Details
ID: {id}
)
}
const styles = StyleSheet.create({
container: { flex: 1, padding: 24 },
heading: { fontSize: 24, fontWeight: 'bold', marginBottom: 8 },
id: { fontSize: 16, color: '#888' },
})
```
เมื่อเข้าถึง `/product/42` หน้าจอนี้จะแสดงขึ้นพร้อมกับตัวแปร `id` ที่มีค่าเป็น `"42"` hook `useLocalSearchParams` ให้การเข้าถึง route parameters ทั้งหมดพร้อม type safety
สำหรับ catch-all routes นั้น `[...slug].tsx` จะจับคู่กับ path segments ทั้งหมด:
```typescript
// app/docs/[...slug].tsx
import { useLocalSearchParams } from 'expo-router'
export default function DocsScreen() {
// /docs/getting-started/installation → slug = ['getting-started', 'installation']
const { slug } = useLocalSearchParams<{ slug: string[] }>()
return
}
```
## Typed Routes สำหรับความปลอดภัยในขั้นตอน Compilation
Expo Router สร้าง route types ขึ้นโดยอัตโนมัติเมื่อเปิดใช้ฟีเจอร์ `typed routes` การตรวจสอบประเภทนี้ช่วยตรวจจับลิงก์ที่เสียตั้งแต่ขั้นตอน compilation แทนที่จะปล่อยให้เกิดข้อผิดพลาดตอน runtime
เปิดใช้งานในไฟล์ `app.json`:
```json
{
"expo": {
"experiments": {
"typedRoutes": true
}
}
}
```
เมื่อเปิดใช้แล้ว prop `href` ของคอมโพเนนต์ `Link` และ argument ของ `router.push()` จะรับเฉพาะ string ที่ตรงกับ route ที่มีอยู่จริงเท่านั้น:
```typescript
// app/checkout.tsx
import { router } from 'expo-router'
function handleCheckout(cartId: string) {
// TypeScript validates this route exists
router.push(`/product/${cartId}`)
// This would cause a compile error if /nonexistent doesn't exist
// router.push('/nonexistent')
}
```
Typed routes ทำงานได้ดีเป็นพิเศษเมื่อใช้ร่วมกับ `useLocalSearchParams` เนื่องจาก types ที่สร้างขึ้นจะรับประกันว่าชื่อ parameter ในการกำหนด route ตรงกับชื่อที่ใช้ในคอมโพเนนต์ที่เรียกใช้ ช่วยป้องกัน bug ที่ยากต่อการตรวจจับซึ่งจะปรากฏก็ต่อเมื่อนำทางไปยังหน้าจอนั้นๆ เท่านั้น
## Modal Screens และตัวเลือกการแสดงผล
Modal ใน Expo Router คือหน้าจอปกติที่กำหนดค่า `presentation: 'modal'` ไว้ใน layout แนวทางนี้ยังคงยึดตามหลักการของ file-based routing กล่าวคือ modal ก็เป็นเพียง route หนึ่งเช่นเดียวกับ route อื่นๆ
```typescript
// app/_layout.tsx
import { Stack } from 'expo-router'
export default function RootLayout() {
return (
{/* Modal screen slides up from the bottom */}
)
}
```
```typescript
// app/create-post.tsx
import { View, TextInput, Button, StyleSheet } from 'react-native'
import { router } from 'expo-router'
import { useState } from 'react'
export default function CreatePostModal() {
const [title, setTitle] = useState('')
const handleSubmit = () => {
// Submit logic here
router.back() // Dismiss the modal
}
return (
)
}
const styles = StyleSheet.create({
container: { flex: 1, padding: 24 },
input: {
borderWidth: 1,
borderColor: '#333',
borderRadius: 8,
padding: 12,
fontSize: 16,
marginBottom: 16,
},
})
```
เมื่อเข้าถึง `/create-post` หน้าจอจะแสดงในรูปแบบ modal เลื่อนขึ้นจากด้านล่าง การเรียก `router.back()` จะปิด modal และนำผู้ใช้กลับไปยังหน้าจอก่อนหน้าใน navigation stack
## การนำทางแบบ Programmatic และ Router API
นอกเหนือจากคอมโพเนนต์ `Link` แล้ว Expo Router ยังมี imperative API ผ่านออบเจ็กต์ `router` ซึ่งช่วยจัดการ navigation ที่ถูกเรียกจาก business logic แทนการกดปุ่มของผู้ใช้โดยตรง
```typescript
import { router } from 'expo-router'
// Push a new screen onto the stack
router.push('/profile/settings')
// Replace the current screen (no back button)
router.replace('/login')
// Go back to the previous screen
router.back()
// Navigate with parameters
router.push({
pathname: '/product/[id]',
params: { id: '42', source: 'recommendations' },
})
// Check if going back is possible
import { useRouter } from 'expo-router'
function BackButton() {
const router = useRouter()
return router.canGoBack() ? (