ไทย

สำรวจรูปแบบการนำทางที่จำเป็นด้วย React Router v6 เรียนรู้การกำหนดเส้นทางแบบประกาศ, เส้นทางแบบไดนามิก, การนำทางด้วยโปรแกรม, เส้นทางซ้อน และกลยุทธ์การโหลดข้อมูลเพื่อสร้างเว็บแอปพลิเคชันที่แข็งแกร่งและใช้งานง่าย

React Router v6: การเรียนรู้รูปแบบการนำทางอย่างเชี่ยวชาญสำหรับเว็บแอปพลิเคชันสมัยใหม่

React Router v6 เป็นไลบรารีการกำหนดเส้นทาง (routing) ที่ทรงพลังและยืดหยุ่นสำหรับแอปพลิเคชัน React ช่วยให้คุณสามารถสร้างแอปพลิเคชันหน้าเดียว (SPAs) ที่มีประสบการณ์ผู้ใช้ที่ราบรื่นโดยการจัดการการนำทางโดยไม่ต้องโหลดหน้าเว็บใหม่ทั้งหมด บทความนี้จะเจาะลึกรูปแบบการนำทางที่จำเป็นโดยใช้ React Router v6 พร้อมให้ความรู้และตัวอย่างเพื่อให้คุณสามารถสร้างเว็บแอปพลิเคชันที่แข็งแกร่งและใช้งานง่าย

ทำความเข้าใจแนวคิดหลักของ React Router v6

ก่อนที่จะเจาะลึกถึงรูปแบบเฉพาะ เรามาทบทวนแนวคิดพื้นฐานบางอย่างกันก่อน:

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;

ในตัวอย่างนี้ เรากำหนดเส้นทางสามเส้นทาง:

คอมโพเนนต์ 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;

ในตัวอย่างนี้:

ตัวอย่างการทำ 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;

ในตัวอย่างนี้:

การส่ง 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

5. การจัดการข้อผิดพลาด "ไม่พบ" (404)

เป็นสิ่งสำคัญที่ต้องจัดการกรณีที่ผู้ใช้นำทางไปยังเส้นทางที่ไม่มีอยู่จริง React Router v6 ทำให้สิ่งนี้ง่ายขึ้นด้วยเส้นทางแบบ catch-all (ดักจับทั้งหมด)

ตัวอย่าง: การสร้างหน้า 404


import { BrowserRouter, Routes, Route, Link } from "react-router-dom";

function NotFound() {
  return (
    

404 - ไม่พบหน้า

หน้าที่คุณกำลังค้นหาไม่มีอยู่

กลับไปหน้าแรก
); } function App() { return ( } /> } /> } /> ); }

ในตัวอย่างนี้:

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 มีกลไกการดึงข้อมูลในตัวที่ผสานรวมกับการกำหนดเส้นทางได้อย่างราบรื่น ไลบรารีเหล่านี้มักมีฟีเจอร์ต่างๆ เช่น:

การใช้ไลบรารีดังกล่าวสามารถทำให้การโหลดข้อมูลง่ายขึ้นอย่างมากและปรับปรุงประสิทธิภาพ โดยเฉพาะอย่างยิ่งสำหรับแอปพลิเคชันที่ซับซ้อน

การเรนเดอร์ฝั่งเซิร์ฟเวอร์ (SSR) และการสร้างไซต์แบบสแตติก (SSG)

เพื่อปรับปรุง SEO และประสิทธิภาพในการโหลดครั้งแรก ลองพิจารณาใช้ SSR หรือ SSG กับเฟรมเวิร์กเช่น Next.js หรือ Gatsby เฟรมเวิร์กเหล่านี้ช่วยให้คุณดึงข้อมูลบนเซิร์ฟเวอร์หรือระหว่าง build time และส่ง HTML ที่เรนเดอร์ไว้ล่วงหน้าไปยังไคลเอนต์ ซึ่งช่วยลดความจำเป็นที่ไคลเอนต์จะต้องดึงข้อมูลในการโหลดครั้งแรก ส่งผลให้ได้รับประสบการณ์ที่รวดเร็วและเป็นมิตรกับ SEO มากขึ้น

7. การทำงานกับ Router ประเภทต่างๆ

React Router v6 มีการติดตั้ง Router ในรูปแบบต่างๆ เพื่อให้เหมาะกับสภาพแวดล้อมและกรณีการใช้งานที่หลากหลาย:

เลือกประเภท Router ที่เหมาะสมกับความต้องการและสภาพแวดล้อมของแอปพลิเคชันของคุณมากที่สุด

สรุป

React Router v6 นำเสนอโซลูชันการกำหนดเส้นทางที่ครอบคลุมและยืดหยุ่นสำหรับแอปพลิเคชัน React ด้วยการทำความเข้าใจและนำรูปแบบการนำทางที่กล่าวถึงในบทความนี้ไปใช้ คุณจะสามารถสร้างเว็บแอปพลิเคชันที่แข็งแกร่ง ใช้งานง่าย และบำรุงรักษาได้ ตั้งแต่การกำหนดเส้นทางแบบประกาศด้วย <Routes> และ <Route> ไปจนถึงเส้นทางแบบไดนามิกพร้อมพารามิเตอร์ URL การนำทางด้วยโปรแกรมด้วย useNavigate และกลยุทธ์การโหลดข้อมูลที่มีประสิทธิภาพ React Router v6 ช่วยให้คุณสร้างประสบการณ์การนำทางที่ราบรื่นสำหรับผู้ใช้ของคุณ ลองพิจารณาสำรวจไลบรารีกำหนดเส้นทางขั้นสูงและเฟรมเวิร์ก SSR/SSG เพื่อการควบคุมและเพิ่มประสิทธิภาพที่ดียิ่งขึ้น อย่าลืมปรับใช้รูปแบบเหล่านี้ให้เข้ากับความต้องการเฉพาะของแอปพลิเคชันของคุณและให้ความสำคัญกับประสบการณ์ผู้ใช้ที่ชัดเจนและใช้งานง่ายอยู่เสมอ