Expo Router trong React Native: Hướng dẫn đầy đủ về định tuyến dựa trên file
Expo Router mang hệ thống định tuyến dựa trên file đến React Native, lấy cảm hứng từ Next.js. Hướng dẫn toàn diện với cấu hình, tab navigation, dynamic routes, modal, middleware và bảo vệ route.

Expo Router đã thay đổi hoàn toàn cách quản lý điều hướng trong các ứng dụng React Native bằng việc áp dụng hệ thống định tuyến dựa trên file, lấy cảm hứng trực tiếp từ Next.js. Với SDK Expo 55 và Expo Router v6, việc cấu hình navigator thủ công đã trở thành quá khứ. Chỉ cần tạo một file trong thư mục app, màn hình tương ứng sẽ tự động có thể truy cập được. Android, iOS và web đều được hỗ trợ thống nhất mà không cần thêm bất kỳ cấu hình nào. Cách tiếp cận này giảm đáng kể độ phức tạp của các dự án đa nền tảng và tăng tốc quá trình phát triển ứng dụng di động.
Các dự án Expo mới đã tích hợp sẵn Expo Router. Lệnh npx create-expo-app@latest --template default@sdk-55 sẽ tạo một dự án được cấu hình sẵn với hệ thống định tuyến dựa trên file. Đối với các dự án hiện có, chỉ cần cài đặt package expo-router và cập nhật entry point của ứng dụng.
Cơ chế hoạt động của định tuyến dựa trên file trong Expo Router
Mỗi file được đặt trong thư mục app sẽ tự động trở thành một route. Đường dẫn của file tương ứng trực tiếp với đường dẫn URL, loại bỏ hoàn toàn nhu cầu sử dụng một file cấu hình tập trung. File app/settings.tsx tạo route /settings, trong khi app/profile/edit.tsx tương ứng với /profile/edit.
Quy ước này mang lại ba lợi thế quan trọng so với cách cấu hình truyền thống bằng React Navigation:
- Không cần cấu hình: route tồn tại ngay khi file được tạo
- Deep linking tự động: mỗi màn hình đều có URL riêng, thuận tiện cho việc chia sẻ và kiểm thử
- Điều hướng có kiểu dữ liệu: TypeScript nhận biết các route hiện có ngay tại thời điểm biên dịch
import { View, Text, StyleSheet } from 'react-native'
import { Link } from 'expo-router'
export default function HomeScreen() {
return (
<View style={styles.container}>
<Text style={styles.title}>Welcome</Text>
{/* Link maps directly to file path */}
<Link href="/settings" style={styles.link}>
Open Settings
</Link>
<Link href="/profile/edit" style={styles.link}>
Edit Profile
</Link>
</View>
)
}
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 },
})Component Link xử lý điều hướng trên tất cả các nền tảng. Trên web, nó tạo ra thẻ anchor với thuộc tính href phù hợp cho SEO. Trên các nền tảng native, nó kích hoạt kiểu điều hướng stack.
Cấu trúc dự án và các file layout
Expo Router sử dụng các file _layout.tsx để định nghĩa các container điều hướng. Mỗi thư mục có thể chứa layout riêng của mình, tạo ra các cấu trúc điều hướng lồng nhau. Layout gốc bao bọc toàn bộ ứng dụng, trong khi các layout con kiểm soát từng phần cụ thể.
Dưới đây là cấu trúc dự án điển hình:
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/privacyCác nhóm route -- thư mục được bao bọi dấu ngoặc đơn -- giúp tổ chức file mà không ảnh hưởng đến URL. Thư mục (tabs) trong ví dụ trên tạo một tab navigator, nhưng các URL vẫn giữ nguyên là /home, /search và /profile thay vì /tabs/home.
import { Stack } from 'expo-router'
export default function RootLayout() {
return (
<Stack
screenOptions={{
headerStyle: { backgroundColor: '#1a1a2e' },
headerTintColor: '#ffffff',
headerTitleStyle: { fontWeight: '600' },
}}
>
<Stack.Screen name="index" options={{ title: 'Home' }} />
<Stack.Screen name="(tabs)" options={{ headerShown: false }} />
<Stack.Screen name="settings" options={{ title: 'Settings' }} />
</Stack>
)
}Layout gốc cũng đóng vai trò là điểm khởi tạo cho việc tải font chữ, cấu hình các provider và các thiết lập toàn cục. Nó thay thế file App.tsx truyền thống với vai trò là entry point của ứng dụng.
Xây dựng hệ thống điều hướng bằng tab
Điều hướng bằng tab yêu cầu một file _layout.tsx nằm bên trong một nhóm route. Expo Router v6 giới thiệu NativeTabs để mang lại trải nghiệm native đặc thù cho từng nền tảng, tuy nhiên component Tabs tiêu chuẩn vẫn đáp ứng được phần lớn các trường hợp sử dụng.
import { Tabs } from 'expo-router'
import { Ionicons } from '@expo/vector-icons'
export default function TabLayout() {
return (
<Tabs
screenOptions={{
tabBarActiveTintColor: '#61DAFB',
tabBarInactiveTintColor: '#888',
tabBarStyle: {
backgroundColor: '#1a1a2e',
borderTopColor: '#2d2d44',
},
}}
>
<Tabs.Screen
name="home"
options={{
title: 'Home',
tabBarIcon: ({ color, size }) => (
<Ionicons name="home" size={size} color={color} />
),
}}
/>
<Tabs.Screen
name="search"
options={{
title: 'Search',
tabBarIcon: ({ color, size }) => (
<Ionicons name="search" size={size} color={color} />
),
}}
/>
<Tabs.Screen
name="profile"
options={{
title: 'Profile',
tabBarIcon: ({ color, size }) => (
<Ionicons name="person" size={size} color={color} />
),
}}
/>
</Tabs>
)
}Mỗi file màn hình tab export một React component tiêu chuẩn. Icon, nhãn và badge của thanh tab được cấu hình thông qua prop options trong layout.
Route động và tham số route
Các đoạn động sử dụng dấu ngoặc vuông trong tên file. File có tên [id].tsx khớp với một đoạn đơn lẻ, trong khi [...slug].tsx bắt tất cả các đoạn còn lại của đường dẫn.
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 (
<View style={styles.container}>
<Stack.Screen options={{ title: `Product ${id}` }} />
<Text style={styles.heading}>Product Details</Text>
<Text style={styles.id}>ID: {id}</Text>
</View>
)
}
const styles = StyleSheet.create({
container: { flex: 1, padding: 24 },
heading: { fontSize: 24, fontWeight: 'bold', marginBottom: 8 },
id: { fontSize: 16, color: '#888' },
})Khi truy cập /product/42, màn hình này sẽ hiển thị với biến id được gán giá trị "42". Hook useLocalSearchParams cung cấp quyền truy cập có kiểu dữ liệu vào toàn bộ tham số route.
Đối với các route catch-all, [...slug].tsx bắt toàn bộ các đoạn đường dẫn:
import { useLocalSearchParams } from 'expo-router'
export default function DocsScreen() {
// /docs/getting-started/installation → slug = ['getting-started', 'installation']
const { slug } = useLocalSearchParams<{ slug: string[] }>()
return <DocViewer path={slug.join('/')} />
}Sẵn sàng chinh phục phỏng vấn React Native?
Luyện tập với mô phỏng tương tác, flashcards và bài kiểm tra kỹ thuật.
Kiểu dữ liệu cho route đảm bảo an toàn tại thời điểm biên dịch
Expo Router tự động sinh các kiểu dữ liệu cho route khi tính năng typed routes được kích hoạt. Việc kiểm tra này phát hiện các liên kết bị hỏng ngay tại thời điểm biên dịch thay vì khi chạy ứng dụng.
Kích hoạt trong file app.json:
{
"expo": {
"experiments": {
"typedRoutes": true
}
}
}Sau khi kích hoạt, prop href của component Link và tham số của router.push() chỉ chấp nhận các chuỗi tương ứng với route hợp lệ:
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')
}Tính năng typed routes phát huy hiệu quả đặc biệt khi kết hợp với useLocalSearchParams. Các kiểu được sinh tự động đảm bảo sự khớp giữa tên tham số trong định nghĩa route và các tham số được sử dụng bởi component, ngăn chặn những lỗi tiềm ẩn chỉ xuất hiện khi điều hướng đến một màn hình cụ thể.
Màn hình modal và tùy chọn hiển thị
Các modal trong Expo Router là những màn hình thông thường được cấu hình với presentation: 'modal' trong layout. Cách tiếp cận này tuân thủ quy ước của hệ thống định tuyến dựa trên file: một modal đơn giản chỉ là một route như bất kỳ route nào khác.
import { Stack } from 'expo-router'
export default function RootLayout() {
return (
<Stack>
<Stack.Screen name="index" />
<Stack.Screen name="(tabs)" options={{ headerShown: false }} />
{/* Modal screen slides up from the bottom */}
<Stack.Screen
name="create-post"
options={{
presentation: 'modal',
headerTitle: 'New Post',
}}
/>
</Stack>
)
}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 (
<View style={styles.container}>
<TextInput
style={styles.input}
placeholder="Post title"
value={title}
onChangeText={setTitle}
/>
<Button title="Publish" onPress={handleSubmit} />
</View>
)
}
const styles = StyleSheet.create({
container: { flex: 1, padding: 24 },
input: {
borderWidth: 1,
borderColor: '#333',
borderRadius: 8,
padding: 12,
fontSize: 16,
marginBottom: 16,
},
})Khi truy cập /create-post, màn hình modal sẽ được hiển thị. Gọi router.back() sẽ đóng modal và đưa người dùng quay lại màn hình trước đó trong ngăn xếp điều hướng.
Điều hướng lập trình và API của router
Ngoài component Link, Expo Router cung cấp một API mệnh lệnh thông qua đối tượng router. API này cho phép quản lý điều hướng được kích hoạt bởi logic nghiệp vụ thay vì bởi tương tác trực tiếp của người dùng.
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() ? (
<Button title="Back" onPress={() => router.back()} />
) : null
}Sự khác biệt giữa router.push và router.replace rất quan trọng trong các luồng xác thực. Sau khi đăng nhập thành công, router.replace('/dashboard') ngăn không cho người dùng quay lại màn hình đăng nhập bằng nút quay lại.
router.replace() thay thế mục hiện tại trong lịch sử điều hướng. Đối với các route guard chuyển hướng người dùng chưa đăng nhập, nên sử dụng <Redirect href="/login" /> trong phần render của component. Phương thức này được kích hoạt trong giai đoạn render và hoạt động chính xác với server-side rendering trên web.
Middleware và bảo vệ route
Expo Router v6 giới thiệu middleware phía server cho logic được áp dụng ở cấp độ route. File +middleware.ts chặn các request trước khi chúng đến component của route, cho phép thực hiện kiểm tra xác thực, chuyển hướng và thao tác với HTTP headers.
import { type MiddlewareRequest } from 'expo-router/server'
export function middleware(request: MiddlewareRequest) {
const { pathname } = request.nextUrl
// Protect dashboard routes
const protectedPaths = ['/dashboard', '/settings', '/profile']
const isProtected = protectedPaths.some(p => pathname.startsWith(p))
if (isProtected) {
const token = request.cookies.get('session')
if (!token) {
return Response.redirect(new URL('/login', request.url))
}
}
return undefined // Continue to route
}Trên các nền tảng native, middleware phía server không được thực thi. Việc bảo vệ route khi đó dựa vào các guard phía client. Mẫu thiết kế phổ biến nhất là bao bọc các layout được bảo vệ bằng một lớp kiểm tra xác thực:
import { Redirect, Stack } from 'expo-router'
import { useAuth } from '@/hooks/useAuth'
export default function AuthenticatedLayout() {
const { isLoggedIn, isLoading } = useAuth()
if (isLoading) return null
if (!isLoggedIn) return <Redirect href="/login" />
return <Stack />
}Middleware phía server chỉ hoạt động trên web với server-side rendering. Đối với các ứng dụng native, việc triển khai các guard phía client trong các component layout là bắt buộc. Kết hợp cả hai phương pháp đảm bảo việc bảo vệ nhất quán trên toàn bộ các nền tảng.
Kết luận
Expo Router v6 mang đến cho hệ sinh thái React Native một cách tiếp cận điều hướng phù hợp với các quy ước đã được thiết lập bởi các framework web hiện đại. Những điểm chính cần ghi nhớ:
- Expo Router v6 thay thế việc cấu hình điều hướng thủ công bằng các quy ước dựa trên file, trong đó mỗi file trong thư mục
apptự động trở thành một route - Các layout được định nghĩa qua file
_layout.tsxtạo ra các cấu trúc điều hướng -- stack, tab và drawer -- mà không cần file cấu hình tập trung - Route động với
[param].tsxvà route catch-all với[...slug].tsxxử lý điều hướng có tham số với hỗ trợ TypeScript đầy đủ - Tính năng typed routes phát hiện các liên kết điều hướng bị hỏng tại thời điểm biên dịch khi được kích hoạt trong
app.json - Màn hình modal, điều hướng lập trình qua
router.push/replace/backvà middleware phía server hoàn thiện bộ công cụ định tuyến - Các nhóm route bằng thư mục trong dấu ngoặc đơn giúp tổ chức mã nguồn mà không ảnh hưởng đến URL, duy trì cấu trúc thư mục rõ ràng khi ứng dụng mở rộng
- Guard phía client trong layout xử lý xác thực trên native, trong khi middleware phía server bao quát web -- kết hợp cả hai đảm bảo bao phủ đa nền tảng
Bắt đầu luyện tập!
Kiểm tra kiến thức với mô phỏng phỏng vấn và bài kiểm tra kỹ thuật.
Thẻ
Chia sẻ
Bài viết liên quan

React Native: Xay Dung Ung Dung Di Dong Hoan Chinh Nam 2026
Huong dan day du ve phat trien ung dung di dong iOS va Android voi React Native. Tu thiet lap moi truong den trien khai, tat ca kien thuc can ban de ban bat dau.

Angular Standalone Components: Huong dan di chuyen va cac phuong phap tot nhat 2026
Huong dan chi tiet di chuyen Angular standalone components. Quy trinh tung buoc xoa NgModules, kich hoat lazy loading va ap dung standalone API trong Angular 21.