Router
ความจริงคือถ้าเราเขียน React แบบเดิม ๆ เรามักจะกองทุกอย่างไว้ที่ App.tsx แล้วใช้ useState เพื่อสลับหน้าไปมา ซึ่งมัน Scale ยากมาก และเนื่องจาก React เป็น Library ไม่ใช่ Framework (อย่าง Next.js) มันเลยไม่มีระบบจัดการเส้นทาง (Routing) มาให้ในตัว
เพื่อให้เห็นภาพชัดเจนก่อนจะไปถึงเครื่องมือ เรามาแยกให้เห็นก่อนว่าระบบจัดการเส้นทางมันทำงานยังไง และทำไมเราถึง “ขาดมันไม่ได้” เมื่อโปรเจกต์เริ่มโต
Router
Section titled “Router”ในสมัยก่อน เวลาเราเปลี่ยนหน้า เว็บจะทำการ “โหลดใหม่ทั้งหน้า” (Refresh) ซึ่งทำให้ช้าและเสียจังหวะ แต่พอเป็น Single Page Application (SPA) แบบ React:
- ไม่มีการโหลดหน้าใหม่: เว็บจะโหลดแค่ครั้งเดียวตอนเข้าครั้งแรก
- Router เข้ามาจัดการ: เมื่อคุณคลิกเมนู Router จะเปลี่ยน URL ให้ (เช่นจาก / เป็น /about) และสลับเอา Component About มาแปะแทนที่หน้าเดิมทันทีโดยไม่เกิดการกระพริบของหน้าจอ
หน้าที่หลักของ Router
- Navigation: จัดการการนำทาง เช่น การกดปุ่มย้อนกลับ (Back) หรือไปข้างหน้า (Forward) ใน Browser ให้ทำงานได้ถูกต้อง
- State Management (URL): เก็บข้อมูลไว้ที่ URL เช่น /products?category=electronics เพื่อให้เราสามารถก๊อปปี้ลิงก์นี้ไปให้เพื่อน แล้วเพื่อนเปิดมาเจอหน้าเดียวกันเป๊ะๆ
- Data Fetching: ใน TanStack Router มันเก่งถึงขั้นที่ว่า พอมันรู้ว่าคุณกำลังจะไปหน้าไหน มันจะแอบไปโหลดข้อมูล (Pre-fetch) เตรียมไว้ให้ก่อนที่คุณจะคลิกเสร็จด้วยซ้ำ!
- Security: ตรวจสอบสิทธิ์ (Auth) เช่น ถ้ายังไม่ได้ Login แล้วพยายามจะเข้าหน้า /admin ตัว Router ก็จะเตะเรากลับไปหน้า /login ทันที 🏗️ หน้าตาของ Router ในเชิงเปรียบเทียบ
โดยใน tutorial นี้จะใช้ Tanstack router
TanStack Router
Section titled “TanStack Router”คือ Type-safe routing library สำหรับ React ที่สร้างโดยทีม TanStack
จุดเด่น:
- Type-safe - type inference อัตโนมัติ
- File-based routing - รองรับ auto-routing จากโครงสร้างไฟล์
- Lightweight - ไม่มี dependencies มาก
ก่อนจะไปลองใช้งาน เรามาติดตั้ง react tanstack router กันก่อน
ติดตั้ง
Section titled “ติดตั้ง”npm install @tanstack/react-router @tanstack/router-devtoolsnpm install -D @tanstack/router-plugin @tanstack/router-cli vite-plugin-tanstack-routerเพื่อให้ระบบ File-based ทำงานได้ลื่นไหล เราต้องลงทะเบียน Plugin ใน Vite
import { defineConfig } from 'vite'import react from '@vitejs/plugin-react'import { TanStackRouterVite } from '@tanstack/router-plugin/vite'
export default defineConfig({ plugins: [ TanStackRouterVite(), // วางไว้ก่อน react() react(), ],})เชื่อมต่อ Router ใน src/main.tsx นี่คือขั้นตอนสุดท้าย คือการสร้าง Router Instance และครอบ App ด้วย RouterProvider
import React from 'react'import ReactDOM from 'react-dom/client'import { RouterProvider, createRouter } from '@tanstack/react-router'
// นำเข้า routeTree ที่ถูกเจนมาอัตโนมัติ (จะเกิดขึ้นหลังรัน dev server)import { routeTree } from './routeTree.gen'
const router = createRouter({ routeTree })
// ลงทะเบียน type เพื่อให้ Link ต่างๆ เป็น Fully Type-safedeclare module '@tanstack/react-router' { interface Register { router: typeof router }}
ReactDOM.createRoot(document.getElementById('root')!).render( <React.StrictMode> <RouterProvider router={router} /> </React.StrictMode>,)โครงสร้างไฟล์
Section titled “โครงสร้างไฟล์”src/└── routes/ ├── __root.tsx # Root layout ├── index.tsx # หน้าแรก (/) ├── catalog.tsx # /catalog ├── cart.tsx # /cart └── login.tsx # /loginการตั้งค่า Route พื้นฐาน
Section titled “การตั้งค่า Route พื้นฐาน”// src/routes/__root.tsx — Root layout ของทุกหน้าimport { createRootRoute, Link, Outlet } from "@tanstack/react-router"
export const Route = createRootRoute({ component: () => ( <div className="min-h-screen bg-gray-50"> <nav className="bg-white shadow-sm"> <div className="max-w-6xl mx-auto px-4 py-3 flex gap-6"> <Link to="/" className="font-bold text-xl text-blue-600"> Adventure Shop </Link> <Link to="/catalog" className="text-gray-700 hover:text-blue-600"> สินค้า </Link> <Link to="/cart" className="text-gray-700 hover:text-blue-600"> ตะกร้า </Link> <Link to="/login" className="text-gray-700 hover:text-blue-600"> เข้าสู่ระบบ </Link> </div> </nav> <main className="max-w-6xl mx-auto px-4 py-8"> <Outlet /> </main> </div> ),})// src/routes/index.tsx — หน้าแรกimport { createFileRoute } from "@tanstack/react-router"
export const Route = createFileRoute("/")({ component: Index,})
function Index() { return ( <div className="text-center py-20"> <h1 className="text-4xl font-bold text-gray-800 mb-4"> ยินดีต้อนรับสู่ Adventure Shop </h1> <p className="text-xl text-gray-600 mb-8"> สินค้าสำหรับนักเดินทางทุกประเภท </p> <a href="/catalog" className="inline-block bg-blue-500 text-white px-6 py-3 rounded-lg font-semibold hover:bg-blue-600 transition-colors" > ดูสินค้า </a> </div> )}
import { Quiz } from "@/components/quiz"
<Quiz quiz={{ id: "tanstack-router-basics", title: "Quiz: TanStack Router พื้นฐาน", questions: [ { id: "q1", type: "single", question: "TanStack Router มี feature อะไรที่เด่น?", options: ["Component-based routing", "File-based routing", "API-based routing", "Manual routing"], correctAnswer: "File-based routing" }, { id: "q2", type: "single", question: "ถ้าต้องการสร้างหน้า /login ต้องสร้างไฟล์ชื่ออะไรในโฟลเดอร์ routes?", options: ["login.jsx", "login.tsx", "login.component.tsx", "login.page.tsx"], correctAnswer: "login.tsx" }, { id: "q3", type: "single", question: "TanStack Router ใช้ style การเขียนอย่างไร?", options: ["Imperative style", "Declarative style", "Functional style", "Object-oriented style"], correctAnswer: "Declarative style" }, { id: "q4", type: "single", question: "ไฟล์ __root.tsx ใช้ทำอะไร?", options: ["สร้างหน้า 404", "Root layout ของทุกหน้า", "กำหนด config ของ router", "สร้าง redirect"], correctAnswer: "Root layout ของทุกหน้า" }, { id: "q5", type: "single", question: "Component ใดใช้สำหรับสร้าง Link ใน TanStack Router?", options: ["<Link>", "<a href>", "<RouterLink>", "<NavLink>"], correctAnswer: "<Link>" } ] }}/>