Cơn ác mộng mang tên cấu hình Navigation thủ công
Nếu đã từng trầy trật với React Native từ những ngày đầu, chắc chắn anh em không lạ gì react-navigation. Dù nó rất mạnh, việc phải định nghĩa thủ công từng Screen trong file App.js thực sự là một cực hình. File config cứ thế phình to lên hàng trăm, thậm chí hàng ngàn dòng code. Mỗi lần thêm một tính năng mới, mình lại phải quay lại file đó để khai báo. Cực kỳ mất thời gian và rất dễ gây nhầm lẫn khi dự án lớn dần.
Mọi thứ đã thay đổi hoàn toàn từ khi Expo Router xuất hiện. Nó bê nguyên tư duy “File-based Routing” từ Next.js sang môi trường Mobile. Hiểu đơn giản: bạn chỉ cần tạo file trong thư mục, Expo sẽ tự hiểu đó là một route. Không còn cảnh phải import/export Navigator thủ công nữa. Dưới đây là cách mình áp dụng Expo Router để xây dựng cấu trúc thư mục chuẩn cho các dự án thực tế.
Tại sao Expo Router lại là ‘chân ái’ mới?
Về bản chất, Expo Router vẫn chạy trên nền react-navigation nhưng đã được tự động hóa hoàn toàn. Thay vì viết code để định nghĩa đường dẫn, bạn sử dụng chính cấu trúc folder của mình.
Đây là 3 điểm khiến mình ‘đổ’ công nghệ này ngay lập tức:
- Native-first: Tối ưu hiệu năng mượt mà cho cả iOS và Android.
- Deep Linking mặc định: Bình thường cấu hình Deep Linking mất cả buổi sáng. Với Expo Router, đường dẫn file chính là URL. Mọi thứ hoạt động ngay lập tức.
- Cấu trúc trực quan: Chỉ cần nhìn vào folder
app/là biết ngay app có bao nhiêu màn hình. Cực kỳ dễ bảo trì.
Xây dựng cấu trúc thư mục chuẩn cho dự án
Đầu tiên, hãy khởi tạo một dự án Expo mới bằng lệnh quen thuộc:
npx create-expo-app@latest my-new-app
Trong hệ sinh thái này, toàn bộ logic điều hướng sẽ nằm gọn trong thư mục app/. Đây là bộ khung mình thường xuyên áp dụng:
app/
├── (auth)/
│ ├── login.tsx
│ └── register.tsx
├── (tabs)/
│ ├── index.tsx (Trang chủ)
│ ├── settings.tsx
│ └── _layout.tsx
├── user/
│ └── [id].tsx (Trang chi tiết user)
├── _layout.tsx (Layout tổng)
└── +not-found.tsx (Trang 404)
1. File _layout.tsx – Bộ khung của ứng dụng
File _layout.tsx đóng vai trò như “xương sống”. Nó chứa các thành phần dùng chung như Header, Footer hoặc các Provider.
Tại file app/_layout.tsx ngoài cùng, mình thường thiết lập Stack chính như sau:
import { Stack } from 'expo-router';
export default function RootLayout() {
return (
<Stack>
<Stack.Screen name="(tabs)" options={{ headerShown: false }} />
<Stack.Screen name="(auth)" options={{ presentation: 'modal' }} />
<Stack.Screen name="user/[id]" options={{ title: 'Thông tin cá nhân' }} />
</Stack>
);
}
2. Group Routes: Tuyệt chiêu với dấu ngoặc đơn ()
Bạn có thắc mắc tại sao mình dùng (auth) hay (tabs) không? Dấu ngoặc đơn giúp nhóm các file lại cho gọn mà không ảnh hưởng đến URL.
Ví dụ: File app/(auth)/login.tsx sẽ có đường dẫn là /login thay vì /auth/login. Cách này giúp cấu trúc thư mục cực kỳ sạch sẽ nhưng URL vẫn ngắn gọn, chuyên nghiệp.
3. Tabs Layout – Thanh điều hướng dưới cùng
Để tạo Tab bar, mình sử dụng file app/(tabs)/_layout.tsx. Expo Router cung cấp sẵn component Tabs rất mạnh mẽ:
import { Tabs } from 'expo-router';
import { Ionicons } from '@expo/vector-icons';
export default function TabLayout() {
return (
<Tabs screenOptions={{ tabBarActiveTintColor: '#007AFF' }}>
<Tabs.Screen
name="index"
options={{
title: 'Home',
tabBarIcon: ({ color }) => <Ionicons name="home" size={28} color={color} />,
}}
/>
<Tabs.Screen
name="settings"
options={{
title: 'Cài đặt',
tabBarIcon: ({ color }) => <Ionicons name="settings" size={28} color={color} />,
}}
/>
</Tabs>
);
}
Dynamic Routes: Xử lý trang chi tiết linh hoạt
Với các trang như chi tiết sản phẩm hay hồ sơ cá nhân, bạn chỉ cần đặt tên file là [id].tsx.
Cùng xem ví dụ tại file app/user/[id].tsx:
import { useLocalSearchParams } from 'expo-router';
import { View, Text } from 'react-native';
export default function UserDetail() {
const { id } = useLocalSearchParams();
return (
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<Text>User ID: {id}</Text>
</View>
);
}
Khi bạn điều hướng tới /user/123, hệ thống tự động gán id = 123. Rất nhanh và gọn!
Kinh nghiệm ‘thực chiến’ khi xử lý dữ liệu
Trong thực tế, mình thường xuyên phải làm việc với API trả về các chuỗi JSON phức tạp. Việc in console.log rồi ngồi căng mắt đọc thực sự rất tốn thời gian.
Mình hay dùng công cụ tại toolcraft.app (VD: JSON Formatter) để làm sạch dữ liệu. Cách này giúp mình định nghĩa Interface TypeScript chuẩn chỉ trong 30 giây, tránh tuyệt đối lỗi undefined khó chịu khi làm Dynamic Routes.
Một mẹo nhỏ nữa: Hãy ưu tiên dùng component Link thay vì useRouter hook. Code của bạn sẽ trông giống HTML, cực kỳ dễ đọc và bảo trì:
import { Link } from 'expo-router';
<Link href="/user/99" asChild>
<Button title="Xem hồ sơ" />
</Link>
Tạm kết
Chuyển sang Expo Router có thể khiến bạn hơi bỡ ngỡ lúc đầu vì cách đặt tên file mới lạ. Tuy nhiên, khi đã quen, bạn sẽ thấy nó tiết kiệm được 30-40% lượng code boilerplate không cần thiết. Hãy thử áp dụng cấu trúc mình gợi ý vào dự án tiếp theo. Chắc chắn bạn sẽ thấy sự khác biệt. Chúc anh em có những giờ phút code thật ‘phiêu’!

