สำรวจรูปแบบการนำทางที่จำเป็นด้วย React Router v6 เรียนรู้การกำหนดเส้นทางแบบประกาศ, เส้นทางแบบไดนามิก, การนำทางด้วยโปรแกรม, เส้นทางซ้อน และกลยุทธ์การโหลดข้อมูลเพื่อสร้างเว็บแอปพลิเคชันที่แข็งแกร่งและใช้งานง่าย
React Router v6: การเรียนรู้รูปแบบการนำทางอย่างเชี่ยวชาญสำหรับเว็บแอปพลิเคชันสมัยใหม่
React Router v6 เป็นไลบรารีการกำหนดเส้นทาง (routing) ที่ทรงพลังและยืดหยุ่นสำหรับแอปพลิเคชัน React ช่วยให้คุณสามารถสร้างแอปพลิเคชันหน้าเดียว (SPAs) ที่มีประสบการณ์ผู้ใช้ที่ราบรื่นโดยการจัดการการนำทางโดยไม่ต้องโหลดหน้าเว็บใหม่ทั้งหมด บทความนี้จะเจาะลึกรูปแบบการนำทางที่จำเป็นโดยใช้ React Router v6 พร้อมให้ความรู้และตัวอย่างเพื่อให้คุณสามารถสร้างเว็บแอปพลิเคชันที่แข็งแกร่งและใช้งานง่าย
ทำความเข้าใจแนวคิดหลักของ React Router v6
ก่อนที่จะเจาะลึกถึงรูปแบบเฉพาะ เรามาทบทวนแนวคิดพื้นฐานบางอย่างกันก่อน:
- การกำหนดเส้นทางแบบประกาศ (Declarative Routing): React Router ใช้วิธีการแบบประกาศ (declarative) โดยที่คุณกำหนดเส้นทางของคุณในรูปแบบของคอมโพเนนต์ React ซึ่งทำให้ตรรกะการกำหนดเส้นทางของคุณชัดเจนและง่ายต่อการบำรุงรักษา
- คอมโพเนนต์ (Components): คอมโพเนนต์หลักประกอบด้วย
BrowserRouter
,HashRouter
,MemoryRouter
,Routes
, และRoute
- Hooks: React Router มี hooks เช่น
useNavigate
,useLocation
,useParams
, และuseRoutes
เพื่อเข้าถึงข้อมูลการกำหนดเส้นทางและจัดการการนำทาง
1. การกำหนดเส้นทางแบบประกาศด้วย <Routes>
และ <Route>
รากฐานของ React Router v6 อยู่ที่การกำหนดเส้นทางแบบประกาศ คุณกำหนดเส้นทางของคุณโดยใช้คอมโพเนนต์ <Routes>
และ <Route>
คอมโพเนนต์ <Routes>
ทำหน้าที่เป็นคอนเทนเนอร์สำหรับเส้นทางของคุณ และคอมโพเนนต์ <Route>
จะกำหนดเส้นทางเฉพาะและคอมโพเนนต์ที่จะแสดงผลเมื่อเส้นทางนั้นตรงกับ URL ปัจจุบัน
ตัวอย่าง: การกำหนดค่าเส้นทางพื้นฐาน
นี่คือตัวอย่างพื้นฐานของการตั้งค่าเส้นทางสำหรับแอปพลิเคชันอย่างง่าย:
import { BrowserRouter, Routes, Route } from "react-router-dom";
import Home from "./pages/Home";
import About from "./pages/About";
import Contact from "./pages/Contact";
function App() {
return (
} />
} />
} />
);
}
export default App;
ในตัวอย่างนี้ เรากำหนดเส้นทางสามเส้นทาง:
/
: แสดงผลคอมโพเนนต์Home
/about
: แสดงผลคอมโพเนนต์About
/contact
: แสดงผลคอมโพเนนต์Contact
คอมโพเนนต์ BrowserRouter
เปิดใช้งานการกำหนดเส้นทางตามประวัติของเบราว์เซอร์ (browser history) React Router จะจับคู่ URL ปัจจุบันกับเส้นทางที่กำหนดไว้และแสดงผลคอมโพเนนต์ที่สอดคล้องกัน
2. เส้นทางแบบไดนามิกพร้อมพารามิเตอร์ URL
เส้นทางแบบไดนามิก (Dynamic routes) ช่วยให้คุณสร้างเส้นทางที่สามารถจัดการกับค่าต่างๆ ใน URL ได้ ซึ่งมีประโยชน์สำหรับการแสดงเนื้อหาตามตัวระบุที่ไม่ซ้ำกัน เช่น ID ของผลิตภัณฑ์หรือ ID ของผู้ใช้ React Router v6 ใช้สัญลักษณ์ :
เพื่อกำหนดพารามิเตอร์ URL
ตัวอย่าง: การแสดงรายละเอียดสินค้า
สมมติว่าคุณมีแอปพลิเคชันอีคอมเมิร์ซและต้องการแสดงรายละเอียดของแต่ละผลิตภัณฑ์ตาม ID ของมัน คุณสามารถกำหนดเส้นทางแบบไดนามิกได้ดังนี้:
import { BrowserRouter, Routes, Route, useParams } from "react-router-dom";
function ProductDetails() {
const { productId } = useParams();
// ดึงข้อมูลรายละเอียดสินค้าตาม productId
// ...
return (
รายละเอียดสินค้า
รหัสสินค้า: {productId}
{/* แสดงรายละเอียดสินค้าที่นี่ */}
);
}
function App() {
return (
} />
);
}
export default App;
ในตัวอย่างนี้:
/products/:productId
กำหนดเส้นทางแบบไดนามิกโดยที่:productId
คือพารามิเตอร์ URL- Hook
useParams
ถูกใช้เพื่อเข้าถึงค่าของพารามิเตอร์productId
ภายในคอมโพเนนต์ProductDetails
- จากนั้นคุณสามารถใช้
productId
เพื่อดึงข้อมูลรายละเอียดผลิตภัณฑ์ที่เกี่ยวข้องจากแหล่งข้อมูลของคุณได้
ตัวอย่างการทำ Internationalization: การจัดการรหัสภาษา
สำหรับเว็บไซต์หลายภาษา คุณอาจใช้เส้นทางแบบไดนามิกเพื่อจัดการรหัสภาษา:
} />
เส้นทางนี้จะตรงกับ URL เช่น /en/about
, /fr/about
, และ /es/about
พารามิเตอร์ lang
สามารถนำไปใช้เพื่อโหลดทรัพยากรภาษาที่เหมาะสมได้
3. การนำทางด้วยโปรแกรมด้วย useNavigate
แม้ว่าการกำหนดเส้นทางแบบประกาศจะเหมาะสำหรับลิงก์แบบคงที่ แต่บ่อยครั้งที่คุณจำเป็นต้องนำทางด้วยโปรแกรมตามการกระทำของผู้ใช้หรือตรรกะของแอปพลิเคชัน React Router v6 มี hook useNavigate
สำหรับวัตถุประสงค์นี้ useNavigate
จะคืนค่าฟังก์ชันที่ช่วยให้คุณสามารถนำทางไปยังเส้นทางต่างๆ ได้
ตัวอย่าง: การเปลี่ยนเส้นทางหลังจากการส่งฟอร์ม
สมมติว่าคุณมีการส่งฟอร์มและต้องการเปลี่ยนเส้นทางผู้ใช้ไปยังหน้าแสดงความสำเร็จหลังจากส่งฟอร์มเรียบร้อยแล้ว:
import { useNavigate } from "react-router-dom";
function MyForm() {
const navigate = useNavigate();
const handleSubmit = async (event) => {
event.preventDefault();
// ส่งข้อมูลฟอร์ม
// ...
// เปลี่ยนเส้นทางไปยังหน้าแสดงความสำเร็จหลังจากการส่งสำเร็จ
navigate("/success");
};
return (
);
}
export default MyForm;
ในตัวอย่างนี้:
- เราใช้ hook
useNavigate
เพื่อรับฟังก์ชันnavigate
- หลังจากที่ฟอร์มถูกส่งเรียบร้อยแล้ว เราเรียกใช้
navigate("/success")
เพื่อเปลี่ยนเส้นทางผู้ใช้ไปยังเส้นทาง/success
การส่ง State ระหว่างการนำทาง
คุณยังสามารถส่ง state ไปพร้อมกับการนำทางได้โดยใช้อาร์กิวเมนต์ที่สองของ navigate
:
navigate("/confirmation", { state: { orderId: "12345" } });
วิธีนี้ช่วยให้คุณส่งข้อมูลไปยังคอมโพเนนต์เป้าหมาย ซึ่งสามารถเข้าถึงได้โดยใช้ hook useLocation
4. เส้นทางซ้อนและเลย์เอาต์ (Nested Routes and Layouts)
เส้นทางซ้อน (Nested routes) ช่วยให้คุณสร้างโครงสร้างการกำหนดเส้นทางแบบลำดับชั้น โดยที่เส้นทางหนึ่งซ้อนอยู่ภายในอีกเส้นทางหนึ่ง ซึ่งมีประโยชน์ในการจัดระเบียบแอปพลิเคชันที่ซับซ้อนซึ่งมีการนำทางหลายระดับ ช่วยในการสร้างเลย์เอาต์ที่องค์ประกอบ UI บางอย่างปรากฏอย่างสม่ำเสมอในส่วนต่างๆ ของแอปพลิเคชัน
ตัวอย่าง: ส่วนโปรไฟล์ผู้ใช้
สมมติว่าคุณมีส่วนโปรไฟล์ผู้ใช้พร้อมเส้นทางซ้อนสำหรับแสดงข้อมูลโปรไฟล์ การตั้งค่า และคำสั่งซื้อของผู้ใช้:
import { BrowserRouter, Routes, Route, Link } from "react-router-dom";
function Profile() {
return (
โปรไฟล์ผู้ใช้
-
ข้อมูลโปรไฟล์
-
การตั้งค่า
-
คำสั่งซื้อ
} />
} />
} />
);
}
function ProfileInformation() {
return คอมโพเนนต์ข้อมูลโปรไฟล์
;
}
function Settings() {
return คอมโพเนนต์การตั้งค่า
;
}
function Orders() {
return คอมโพเนนต์คำสั่งซื้อ
;
}
function App() {
return (
} />
);
}
export default App;
ในตัวอย่างนี้:
- เส้นทาง
/profile/*
จะจับคู่กับ URL ใดๆ ที่ขึ้นต้นด้วย/profile
- คอมโพเนนต์
Profile
จะแสดงผลเมนูการนำทางและคอมโพเนนต์<Routes>
เพื่อจัดการเส้นทางที่ซ้อนกัน - เส้นทางที่ซ้อนกันจะกำหนดคอมโพเนนต์ที่จะแสดงผลสำหรับ
/profile/info
,/profile/settings
, และ/profile/orders
เครื่องหมาย *
ในเส้นทางหลักมีความสำคัญอย่างยิ่ง มันหมายความว่าเส้นทางหลักควรจะจับคู่กับเส้นทางย่อยใดๆ ก็ตาม ทำให้เส้นทางที่ซ้อนกันสามารถจับคู่ได้อย่างถูกต้องภายในคอมโพเนนต์ Profile
5. การจัดการข้อผิดพลาด "ไม่พบ" (404)
เป็นสิ่งสำคัญที่ต้องจัดการกรณีที่ผู้ใช้นำทางไปยังเส้นทางที่ไม่มีอยู่จริง React Router v6 ทำให้สิ่งนี้ง่ายขึ้นด้วยเส้นทางแบบ catch-all (ดักจับทั้งหมด)
ตัวอย่าง: การสร้างหน้า 404
import { BrowserRouter, Routes, Route, Link } from "react-router-dom";
function NotFound() {
return (
404 - ไม่พบหน้า
หน้าที่คุณกำลังค้นหาไม่มีอยู่
กลับไปหน้าแรก
);
}
function App() {
return (
} />
} />
} />
);
}
ในตัวอย่างนี้:
- เส้นทาง
<Route path="*" element={<NotFound />} />
เป็นเส้นทางแบบ catch-all ที่จะจับคู่กับ URL ใดๆ ที่ไม่ตรงกับเส้นทางอื่นที่กำหนดไว้ - สิ่งสำคัญคือต้องวางเส้นทางนี้ไว้ท้ายสุดของคอมโพเนนต์
<Routes>
เพื่อให้มันจับคู่ก็ต่อเมื่อไม่มีเส้นทางอื่นตรงกัน
6. กลยุทธ์การโหลดข้อมูลด้วย React Router v6
React Router v6 ไม่มีกลไกการโหลดข้อมูลในตัวเหมือนเวอร์ชันก่อนหน้า (React Router v5 ที่มี `useRouteMatch`) อย่างไรก็ตาม มันมีเครื่องมือสำหรับนำกลยุทธ์การโหลดข้อมูลต่างๆ มาใช้งานได้อย่างมีประสิทธิภาพ
ทางเลือกที่ 1: การดึงข้อมูลในคอมโพเนนต์
วิธีที่ง่ายที่สุดคือการดึงข้อมูลโดยตรงภายในคอมโพเนนต์ที่แสดงผลเส้นทางนั้น คุณสามารถใช้ hook useEffect
เพื่อดึงข้อมูลเมื่อคอมโพเนนต์ถูก mount หรือเมื่อพารามิเตอร์ URL เปลี่ยนแปลง
import { useParams } from "react-router-dom";
import { useEffect, useState } from "react";
function ProductDetails() {
const { productId } = useParams();
const [product, setProduct] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
async function fetchProduct() {
try {
const response = await fetch(`/api/products/${productId}`);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
setProduct(data);
setLoading(false);
} catch (e) {
setError(e);
setLoading(false);
}
}
fetchProduct();
}, [productId]);
if (loading) return กำลังโหลด...
;
if (error) return ข้อผิดพลาด: {error.message}
;
if (!product) return ไม่พบสินค้า
;
return (
{product.name}
{product.description}
);
}
export default ProductDetails;
แนวทางนี้ตรงไปตรงมาแต่อาจนำไปสู่โค้ดที่ซ้ำซ้อนหากคุณต้องการดึงข้อมูลในหลายคอมโพเนนต์ นอกจากนี้ยังไม่มีประสิทธิภาพเท่าที่ควรเนื่องจากการดึงข้อมูลจะเริ่มขึ้นหลังจากที่คอมโพเนนต์ถูก mount แล้วเท่านั้น
ทางเลือกที่ 2: การใช้ Custom Hook สำหรับการดึงข้อมูล
เพื่อลดความซ้ำซ้อนของโค้ด คุณสามารถสร้าง custom hook ที่ห่อหุ้มตรรกะการดึงข้อมูลไว้ได้ จากนั้น hook นี้สามารถนำกลับมาใช้ใหม่ได้ในหลายคอมโพเนนต์
import { useState, useEffect } from "react";
function useFetch(url) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
async function fetchData() {
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const json = await response.json();
setData(json);
setLoading(false);
} catch (e) {
setError(e);
setLoading(false);
}
}
fetchData();
}, [url]);
return { data, loading, error };
}
export default useFetch;
จากนั้น คุณสามารถใช้ hook นี้ในคอมโพเนนต์ของคุณได้:
import { useParams } from "react-router-dom";
import useFetch from "./useFetch";
function ProductDetails() {
const { productId } = useParams();
const { data: product, loading, error } = useFetch(`/api/products/${productId}`);
if (loading) return กำลังโหลด...
;
if (error) return ข้อผิดพลาด: {error.message}
;
if (!product) return ไม่พบสินค้า
;
return (
{product.name}
{product.description}
);
}
export default ProductDetails;
ทางเลือกที่ 3: การใช้ไลบรารีกำหนดเส้นทางที่มีความสามารถในการดึงข้อมูล (TanStack Router, Remix)
ไลบรารีอย่าง TanStack Router และ Remix มีกลไกการดึงข้อมูลในตัวที่ผสานรวมกับการกำหนดเส้นทางได้อย่างราบรื่น ไลบรารีเหล่านี้มักมีฟีเจอร์ต่างๆ เช่น:
- Loaders: ฟังก์ชันที่ทำงาน *ก่อน* ที่เส้นทางจะถูกแสดงผล ทำให้คุณสามารถดึงข้อมูลและส่งต่อไปยังคอมโพเนนต์ได้
- Actions: ฟังก์ชันที่จัดการการส่งฟอร์มและการเปลี่ยนแปลงข้อมูล (data mutations)
การใช้ไลบรารีดังกล่าวสามารถทำให้การโหลดข้อมูลง่ายขึ้นอย่างมากและปรับปรุงประสิทธิภาพ โดยเฉพาะอย่างยิ่งสำหรับแอปพลิเคชันที่ซับซ้อน
การเรนเดอร์ฝั่งเซิร์ฟเวอร์ (SSR) และการสร้างไซต์แบบสแตติก (SSG)
เพื่อปรับปรุง SEO และประสิทธิภาพในการโหลดครั้งแรก ลองพิจารณาใช้ SSR หรือ SSG กับเฟรมเวิร์กเช่น Next.js หรือ Gatsby เฟรมเวิร์กเหล่านี้ช่วยให้คุณดึงข้อมูลบนเซิร์ฟเวอร์หรือระหว่าง build time และส่ง HTML ที่เรนเดอร์ไว้ล่วงหน้าไปยังไคลเอนต์ ซึ่งช่วยลดความจำเป็นที่ไคลเอนต์จะต้องดึงข้อมูลในการโหลดครั้งแรก ส่งผลให้ได้รับประสบการณ์ที่รวดเร็วและเป็นมิตรกับ SEO มากขึ้น
7. การทำงานกับ Router ประเภทต่างๆ
React Router v6 มีการติดตั้ง Router ในรูปแบบต่างๆ เพื่อให้เหมาะกับสภาพแวดล้อมและกรณีการใช้งานที่หลากหลาย:
- BrowserRouter: ใช้ HTML5 history API (
pushState
,replaceState
) สำหรับการนำทาง เป็นตัวเลือกที่พบบ่อยที่สุดสำหรับเว็บแอปพลิเคชันที่ทำงานในสภาพแวดล้อมของเบราว์เซอร์ - HashRouter: ใช้ส่วน hash ของ URL (
#
) สำหรับการนำทาง ซึ่งมีประโยชน์สำหรับแอปพลิเคชันที่ต้องรองรับเบราว์เซอร์รุ่นเก่าหรือที่ถูกปรับใช้บนเซิร์ฟเวอร์ที่ไม่รองรับ HTML5 history API - MemoryRouter: เก็บประวัติของ "URL" ของคุณไว้ในหน่วยความจำ (อาร์เรย์ของ URL) มีประโยชน์ในสภาพแวดล้อมเช่น React Native และการทดสอบ
เลือกประเภท Router ที่เหมาะสมกับความต้องการและสภาพแวดล้อมของแอปพลิเคชันของคุณมากที่สุด
สรุป
React Router v6 นำเสนอโซลูชันการกำหนดเส้นทางที่ครอบคลุมและยืดหยุ่นสำหรับแอปพลิเคชัน React ด้วยการทำความเข้าใจและนำรูปแบบการนำทางที่กล่าวถึงในบทความนี้ไปใช้ คุณจะสามารถสร้างเว็บแอปพลิเคชันที่แข็งแกร่ง ใช้งานง่าย และบำรุงรักษาได้ ตั้งแต่การกำหนดเส้นทางแบบประกาศด้วย <Routes>
และ <Route>
ไปจนถึงเส้นทางแบบไดนามิกพร้อมพารามิเตอร์ URL การนำทางด้วยโปรแกรมด้วย useNavigate
และกลยุทธ์การโหลดข้อมูลที่มีประสิทธิภาพ React Router v6 ช่วยให้คุณสร้างประสบการณ์การนำทางที่ราบรื่นสำหรับผู้ใช้ของคุณ ลองพิจารณาสำรวจไลบรารีกำหนดเส้นทางขั้นสูงและเฟรมเวิร์ก SSR/SSG เพื่อการควบคุมและเพิ่มประสิทธิภาพที่ดียิ่งขึ้น อย่าลืมปรับใช้รูปแบบเหล่านี้ให้เข้ากับความต้องการเฉพาะของแอปพลิเคชันของคุณและให้ความสำคัญกับประสบการณ์ผู้ใช้ที่ชัดเจนและใช้งานง่ายอยู่เสมอ