[React Basics] Cách dựng Nested Routes trong React hiệu quả nhất

Bạn đã bao giờ xây dựng một giao diện người dùng (UI) có các phần không đổi, ví dụ như một thanh điều hướng (sidebar) hay một menu chung, trong khi nội dung chính lại thay đổi liên tục chưa? Bạn loay hoay với việc quản lý state và render các component một cách có điều kiện? Nếu câu trả lời là có, thì Nested Routes (Các tuyến đường lồng nhau) trong React chính là chìa khóa vàng mà bạn đang tìm kiếm. 🔑

Cách dựng Nested Routes trong React hiệu quả nhất

Trong bài viết này, chúng ta sẽ cùng nhau "mổ xẻ" mọi ngóc ngách của Nested Routes: từ khái niệm cốt lõi, lợi ích vượt trội, cho đến cách triển khai chi tiết với React Router v6 qua những ví dụ trực quan nhất. Hãy sẵn sàng để nâng tầm kỹ năng phát triển ứng dụng React của bạn! 🚀

🧐 Nested Routes là gì? Một cách nhìn trực quan

Hãy tưởng tượng giao diện ứng dụng của bạn giống như một hệ thống thư mục trên máy tính. Bạn có thư mục gốc /users, bên trong đó có thể chứa danh sách tất cả người dùng. Khi bạn nhấp vào một người dùng cụ thể, bạn đi sâu hơn vào /users/123 để xem chi tiết. Và nếu bạn muốn chỉnh sửa thông tin của người dùng đó, bạn lại đi tiếp đến /users/123/edit.

Profile Edit - UI design

Đó chính là ý tưởng cốt lõi của Nested Routes! Nested Routes là cách cấu trúc các route của bạn theo kiểu cha-con, cho phép các component UI được lồng vào nhau, phản ánh chính xác cấu trúc của URL.

Nói một cách đơn giản:

  • Một route cha sẽ định nghĩa một layout hoặc một phần giao diện chung.
  • Các route con sẽ được render bên trong layout của route cha đó.

Điều này cho phép một phần của giao diện (cha) tồn tại cố định trong khi các phần khác (con) thay đổi dựa trên URL.

✨ Tại sao phải "làm tổ"? Lợi ích vượt trội của Nested Routes

Sử dụng Nested Routes không chỉ là một kỹ thuật "cho vui", nó mang lại những lợi ích vô cùng thiết thực trong quá trình phát triển ứng dụng.

1. Tái sử dụng Layout ♻️

Đây là lợi ích lớn nhất. Thay vì phải import và render một Sidebar hay DashboardLayout trong mỗi component con, bạn chỉ cần định nghĩa nó một lần ở route cha. Mọi route con sẽ tự động được "bao bọc" bởi layout đó. Điều này giúp code của bạn DRY (Don't Repeat Yourself), sạch sẽ và dễ bảo trì hơn rất nhiều.

2. Tổ chức code rõ ràng, mạch lạc 📁

Cấu trúc route lồng nhau giúp tổ chức các component của bạn một cách logic, tương ứng với cấu trúc URL. Khi nhìn vào file định nghĩa route, bạn có thể ngay lập tức hiểu được mối quan hệ phân cấp giữa các trang và các tính năng trong ứng dụng.

3. Quản lý state hiệu quả hơn 🧠

Vì component cha luôn được render cùng với các component con, bạn có thể dễ dàng quản lý và chia sẻ state từ cha xuống con. Ví dụ, component layout của trang dashboard có thể fetch dữ liệu người dùng một lần và truyền nó xuống cho các route con như "Profile", "Settings", "Notifications".

🛠️ "Làm tổ" cùng React Router v6: Hướng dẫn chi tiết

Bây giờ là phần hấp dẫn nhất! Chúng ta sẽ cùng nhau xây dựng một ví dụ thực tế về trang quản lý sản phẩm bằng React Router v6.

Các component chính cần dùng

Trước khi bắt đầu, hãy làm quen với những cái tên chủ chốt:

  1. <Routes><Route>: Nền tảng để định nghĩa các tuyến đường.
  2. <Outlet />: Đây chính là "ngôi sao" của Nested Routes. Nó hoạt động như một placeholder (trình giữ chỗ). Bất cứ khi nào một route con khớp với URL, component của route con đó sẽ được render ngay tại vị trí của <Outlet />.
  3. <Link> hoặc <NavLink>: Dùng để tạo các liên kết điều hướng giữa các trang.

Ví dụ: Dựng trang quản lý sản phẩm

Hãy tưởng tượng chúng ta có một trang /products với layout chung bao gồm danh sách sản phẩm ở bên trái và khu vực hiển thị chi tiết ở bên phải.

Bước 1: Cấu trúc Route lồng nhau trong App.js

Đầu tiên, chúng ta sẽ định nghĩa cấu trúc route. Route /products sẽ là cha, và các route con sẽ được lồng bên trong nó.

// src/App.js
import { BrowserRouter, Routes, Route } from 'react-router-dom'
import Home from './pages/Home'
import ProductsLayout from './pages/ProductsLayout'
import ProductList from './pages/ProductList'
import ProductDetail from './pages/ProductDetail'
import NewProduct from './pages/NewProduct'

function App() {
  return (
    <BrowserRouter>
      <Routes>
        <Route path="/" element={<Home />} />

        {/* Đây là Route Cha */}
        <Route path="products" element={<ProductsLayout />}>
          {/* Các Route Con sẽ được render bên trong <Outlet /> của ProductsLayout */}
          <Route index element={<ProductList />} />{' '}
          {/* 'index' route cho path mặc định '/products' */}
          <Route path=":id" element={<ProductDetail />} />{' '}
          {/* Route cho '/products/1', '/products/2'... */}
          <Route path="new" element={<NewProduct />} />{' '}
          {/* Route cho '/products/new' */}
        </Route>
      </Routes>
    </BrowserRouter>
  )
}

export default App

Lưu ý:

  • Route con được đặt bên trong thẻ <Route> cha.
  • Route index là route mặc định sẽ được hiển thị khi URL khớp chính xác với route cha (/products).
  • Path của route con là tương đối so với path của cha. Ví dụ, path=":id" sẽ khớp với /products/:id.

Bước 2: Dựng component layout cha ProductsLayout.js

Đây là component định nghĩa giao diện chung và chứa "cánh cổng" <Outlet />.

// src/pages/ProductsLayout.js
import { Outlet, Link } from 'react-router-dom'

function ProductsLayout() {
  return (
    <div style={{ display: 'flex' }}>
      <nav style={{ borderRight: '1px solid #ccc', padding: '1rem' }}>
        <h2>Sản phẩm</h2>
        {/* Các link điều hướng đến các route con */}
        <Link to="/products/1">Sản phẩm 1</Link>
        <br />
        <Link to="/products/2">Sản phẩm 2</Link>
        <br />
        <Link to="/products/new">Thêm sản phẩm mới</Link>
      </nav>

      <main style={{ padding: '1rem' }}>
        <h1>Chi tiết sản phẩm</h1>
        {/* Đây là nơi các component con sẽ được render! */}
        <Outlet />
      </main>
    </div>
  )
}

export default ProductsLayout

Bước 3: Tạo các component con

Đây là các component sẽ "chui" vào vị trí của <Outlet />.

// src/pages/ProductList.js
// Component hiển thị khi người dùng ở trang /products
function ProductList() {
  return <p>Vui lòng chọn một sản phẩm để xem chi tiết.</p>
}

// src/pages/ProductDetail.js
// Component hiển thị khi người dùng ở trang /products/:id
import { useParams } from 'react-router-dom'

function ProductDetail() {
  const { id } = useParams() // Hook để lấy tham số động từ URL
  return <h2>Đây là trang chi tiết cho Sản phẩm có ID: {id}</h2>
}

// src/pages/NewProduct.js
// Component hiển thị khi người dùng ở trang /products/new
function NewProduct() {
  return <h2>Đây là trang để tạo một sản phẩm mới.</h2>
}

// Đừng quên export các component này
export { ProductList, ProductDetail, NewProduct }

Khi bạn chạy ứng dụng này:

  • Truy cập /products, bạn sẽ thấy ProductsLayout và bên trong <Outlet /> là component ProductList.
  • Nhấp vào link "Sản phẩm 1" (đến /products/1), layout ProductsLayout vẫn giữ nguyên, nhưng ProductList được thay thế bằng ProductDetail hiển thị "ID: 1".
  • Nhấp vào "Thêm sản phẩm mới", ProductDetail lại được thay thế bằng NewProduct.

Thật kỳ diệu phải không?

🎯 Các trường hợp sử dụng phổ biến

Nested Routes giải quyết tốt rất nhiều kịch bản thực tế:

  • Dashboards: Một sidebar cố định và phần nội dung chính thay đổi (thống kê, quản lý người dùng, cài đặt).
  • Giao diện Tab: Các tab (Thông tin, Đánh giá, Lịch sử) đều là các route con của một trang chi tiết sản phẩm.
  • Quy trình nhiều bước (Wizards): Mỗi bước trong một form đăng ký hoặc thanh toán là một route con, giúp quản lý trạng thái và điều hướng dễ dàng.
  • Trang cài đặt người dùng: Một menu chung bên trái (Hồ sơ, Bảo mật, Thông báo) và nội dung tương ứng hiển thị bên phải.

Kết luận: Nested Routes là một mô hình mạnh mẽ

Nested Routes không chỉ là một tính năng của React Router, nó là một tư duy, một mô hình mạnh mẽ để thiết kế các ứng dụng React có cấu trúc phức tạp một cách gọn gàng, hiệu quả và dễ dàng mở rộng. Bằng cách nắm vững khái niệm về <Outlet /> và cách tổ chức route cha-con, bạn đã có trong tay một công cụ đắc lực để chinh phục những giao diện dù là "khó nhằn" nhất.

Hãy bắt đầu "làm tổ" cho ứng dụng của bạn ngay hôm nay và cảm nhận sự khác biệt!

Bài viết liên quan

[React Basics] Cách sử dụng Conditional Rendering trong React hiệu quả nhất

Nâng cao kỹ năng React của bạn với Conditional Rendering. Hướng dẫn này sẽ giải thích cách hiển thị các phần tử khác nhau dựa trên điều kiện, tối ưu hóa hiệu suất ứng dụng.

[React Basics] Hiểu rõ cách dùng useRef Hook trong React với các ví dụ thực tế

useRef hook là gì? Tìm hiểu cách sử dụng useRef trong React để tương tác với DOM, lưu trữ giá trị mà không gây re-render. Kèm theo ví dụ code chi tiết và dễ hiểu.

[React Basics] React Context: Khái niệm & Cách sử dụng hiệu quả nhất

React Context là gì? Học cách sử dụng React Context API để quản lý state toàn cục dễ dàng và hiệu quả, loại bỏ Prop Drilling và tối ưu hóa ứng dụng React.

[React Basics] React là gì? Tại sao nên học React ngay hôm nay?

Tìm hiểu React là gì, tại sao nó là một trong những thư viện JavaScript phổ biến nhất thế giới. Nắm bắt được cách React xây dựng giao diện người dùng nhanh chóng và hiệu quả.