Chào mừng các bạn quay trở lại với series “React Roadmap“! Sau khi đã cùng nhau khám phá những khối xây dựng cơ bản của React như React là gì, Component, JSX, Props vs State, và cả thế giới Hooks mạnh mẽ với useState, useEffect, Custom Hooks, giờ là lúc chúng ta đưa ứng dụng của mình lên một tầm cao mới: xử lý định tuyến (routing).
Trong các ứng dụng web truyền thống, khi bạn click vào một đường link, trình duyệt sẽ tải lại toàn bộ trang mới từ server. Nhưng với các Ứng dụng Trang Đơn (Single Page Applications – SPAs) được xây dựng bằng React, chúng ta muốn chuyển đổi giữa các “trang” (thực chất là các Component khác nhau) mà không cần tải lại trang web. Đây chính là lúc thư viện định tuyến phát huy tác dụng. Và khi nói đến định tuyến trong React, React Router là lựa chọn phổ biến và mạnh mẽ nhất.
React Router đã trải qua nhiều phiên bản cải tiến, và phiên bản 6 đánh dấu một bước tiến lớn với nhiều thay đổi giúp việc định tuyến trở nên đơn giản, hiệu quả và gần gũi hơn với cách chúng ta xây dựng Component. Bài viết này sẽ là kim chỉ nam toàn diện giúp bạn làm chủ React Router v6, từ những khái niệm cơ bản đến các kỹ thuật nâng cao.
Mục lục
Tại sao cần Routing trong SPAs?
Một ứng dụng SPA hoạt động trên một trang HTML duy nhất. Khi người dùng tương tác (như click link), thay vì gửi yêu cầu mới lên server để tải trang khác, JavaScript sẽ can thiệp: nó thay đổi nội dung hiện tại của trang để hiển thị giao diện tương ứng với đường dẫn (URL) mới. Điều này mang lại trải nghiệm mượt mà, nhanh chóng cho người dùng.
Tuy nhiên, để làm được điều đó, chúng ta cần một cách để:
- Đồng bộ URL với giao diện: Khi người dùng truy cập
/about
, ứng dụng phải hiển thị ComponentAboutPage
. Khi họ truy cập/contact
, hiển thịContactPage
. - Điều hướng: Cung cấp cách để chuyển từ trang này sang trang khác thông qua các liên kết hoặc bằng code.
- Quản lý lịch sử trình duyệt: Cho phép nút “Back” và “Forward” của trình duyệt hoạt động đúng với các “trang” ảo trong SPA.
React Router cung cấp bộ công cụ mạnh mẽ để giải quyết tất cả những vấn đề này một cách “React-way”.
Cài đặt React Router 6
Việc cài đặt rất đơn giản thông qua npm hoặc yarn. Chúng ta thường cài đặt gói react-router-dom
vì nó bao gồm các Component và Hooks dành cho môi trường trình duyệt web.
npm install react-router-dom
# hoặc
yarn add react-router-dom
Sau khi cài đặt, chúng ta đã sẵn sàng sử dụng React Router trong ứng dụng của mình.
Thiết lập Cơ bản: BrowserRouter và Routes
Thành phần cốt lõi đầu tiên bạn cần là một Router component để “bao bọc” ứng dụng của bạn. BrowserRouter
là loại Router phổ biến nhất cho các ứng dụng web hiện đại sử dụng History API của trình duyệt để giữ cho UI đồng bộ với URL.
Tiếp theo là Routes
(trong v5 là Switch
) và Route
. Routes
là nơi bạn định nghĩa tất cả các tuyến đường (route) của ứng dụng. Nó sẽ lắng nghe URL hiện tại và render Route
phù hợp đầu tiên khớp với URL đó.
Dưới đây là cấu trúc cơ bản trong file App.js
hoặc file entry point của ứng dụng:
// src/App.js
import React from 'react';
import { BrowserRouter, Routes, Route } from 'react-router-dom';
import HomePage from './pages/HomePage';
import AboutPage from './pages/AboutPage';
import ContactPage from './pages/ContactPage';
import NotFoundPage from './pages/NotFoundPage';
import Layout from './components/Layout'; // Chúng ta sẽ nói về layout sau
function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<Layout />} >
{/* Các Route con sẽ được render bên trong Layout */}
<Route index element={<HomePage />} /> {/* Trang chủ */}
<Route path="about" element={<AboutPage />} />
<Route path="contact" element={<ContactPage />} />
{/* Route 404 */}
<Route path="*" element={<NotFoundPage />} />
</Route>
</Routes>
</BrowserRouter>
);
}
export default App;
Giải thích:
<BrowserRouter>
: Bao bọc toàn bộ ứng dụng để kích hoạt tính năng định tuyến.<Routes>
: Đây là container cho tất cả các<Route>
của bạn. React Router v6 sử dụng một thuật toán so khớp tốt hơn so vớiSwitch
trong v5. Nó chọn<Route>
phù hợp nhất, không chỉ là cái đầu tiên khớp.<Route path="..." element={...} />
: Đây là cách định nghĩa một tuyến đường.path
: Là đường dẫn URL mà route này sẽ khớp.element
: Là Component React (một JSX element) sẽ được render khi đường dẫn khớp. Đây là thay đổi lớn so với v5 (thay chocomponent
hoặcrender
prop). Việc sử dụngelement
giúp truyền props dễ dàng hơn và phù hợp với cách React hoạt động với Component.
<Route path="*" element={<NotFoundPage />} />
: Route này vớipath="*"
hoạt động như một route “bắt tất cả” (catch-all), dùng để hiển thị trang 404 khi không có route nào khác khớp. Nó nên được đặt ở cuối cùng.
Điều hướng với Link và NavLink
Để người dùng có thể chuyển đổi giữa các trang, chúng ta sử dụng các Component Link
hoặc NavLink
thay vì thẻ <a>
truyền thống.
<Link to="...">
: Render ra một thẻ<a>
nhưng ngăn hành vi tải lại trang mặc định, thay vào đó sử dụng React Router để thay đổi URL và render Component phù hợp.<NavLink to="...">
: Tương tự nhưLink
nhưng có thêm khả năng tự động thêm classactive
vào thẻ<a>
khi URL hiện tại khớp với đường dẫn củaNavLink
. Điều này rất hữu ích khi xây dựng thanh điều hướng (navigation bar).
// src/components/Navbar.js
import React from 'react';
import { NavLink } from 'react-router-dom';
function Navbar() {
// Hàm style để tùy chỉnh active class (v6)
let activeStyle = {
textDecoration: "underline",
fontWeight: "bold"
};
return (
<nav>
<ul>
<li>
<NavLink
to="/"
style={({ isActive }) =>
isActive ? activeStyle : undefined
}
>
Trang Chủ
</NavLink>
</li>
<li>
<NavLink
to="/about"
className={({ isActive }) =>
isActive ? "active-link" : ""
}
>
Giới Thiệu
</NavLink>
</li>
<li>
<NavLink
to="/contact"
end // Sử dụng 'end' prop để khớp chính xác, tránh /contact/abc vẫn active
className={({ isActive }) =>
isActive ? "active-link" : ""
}
>
Liên Hệ
</NavLink>
</li>
</ul>
</nav>
);
}
export default Navbar;
Bạn sẽ đặt Component Navbar
này trong Layout
Component để nó xuất hiện trên mọi trang.
Nested Routes và Layouts
Một trong những cải tiến lớn và tiện lợi nhất trong React Router v6 là cách xử lý các route lồng nhau (nested routes) và layouts. Thay vì phải tự quản lý việc render các Component con, v6 cho phép bạn định nghĩa cấu trúc route lồng nhau ngay trong Component <Route>
.
Quay lại ví dụ App.js
ở trên, chúng ta có <Route path="/" element={<Layout />} >
và các route con bên trong. Điều này có nghĩa là các route con như /
, /about
, /contact
sẽ được render bên trong Component Layout
.
Component Layout
cần sử dụng Hook useOutlet
hoặc Component <Outlet>
để chỉ định nơi mà các route con sẽ được render:
// src/components/Layout.js
import React from 'react';
import { Outlet } from 'react-router-dom';
import Navbar from './Navbar';
import Footer from './Footer'; // Giả định có Component Footer
function Layout() {
return (
<div>
<Navbar />
<main>
{/* Nơi các Route con (HomePage, AboutPage, ContactPage) sẽ được render */}
<Outlet />
</main>
<Footer />
</div>
);
}
export default Layout;
Với cấu trúc này, Navbar và Footer sẽ luôn hiển thị, còn nội dung chính (HomePage, AboutPage,…) sẽ thay đổi dựa trên URL. Điều này giúp quản lý layout ứng dụng cực kỳ hiệu quả và trực quan.
Lưu ý rằng route con với path="/"
trong v5 cần exact
prop để chỉ khớp chính xác đường dẫn gốc. Trong v6, khi sử dụng cấu trúc lồng nhau với <Routes>
, bạn sử dụng index
prop trên <Route>
con để chỉ định route mặc định cho path của route cha (trong ví dụ này là /
).
Dynamic Routing: Tham số URL (URL Parameters)
Nhiều ứng dụng cần hiển thị nội dung dựa trên một ID hoặc slug trong URL, ví dụ: /users/123
hoặc /products/iphone-15
. React Router cho phép bạn định nghĩa các tham số này trong path
bằng dấu hai chấm (:
).
// src/App.js (trong <Routes>...)
<Route path="users/:userId" element={<UserProfile />} />
Để truy cập giá trị của tham số trong Component UserProfile
, bạn sử dụng Hook useParams
:
// src/pages/UserProfile.js
import React from 'react';
import { useParams } from 'react-router-dom';
function UserProfile() {
let { userId } = useParams();
return (
<div>
<h2>Thông tin Người dùng #{userId}</h2>
{/* Fetch dữ liệu người dùng dựa trên userId và hiển thị */}
<p>Đang tải dữ liệu cho người dùng với ID: {userId}...</p>
</div>
);
}
export default UserProfile;
Hook useParams
trả về một object với các cặp key-value tương ứng với tên tham số trong path và giá trị của chúng từ URL.
Điều hướng bằng Code: useNavigate Hook
Không phải lúc nào việc điều hướng cũng diễn ra khi người dùng click vào một đường link. Đôi khi bạn cần chuyển hướng người dùng sau một hành động nào đó, ví dụ: sau khi gửi form thành công, hoặc sau khi đăng nhập. Trong React Router v6, bạn sử dụng Hook useNavigate
(thay thế cho useHistory
trong v5) để làm điều này.
import React, { useState } from 'react';
import { useNavigate } from 'react-router-dom';
function LoginForm() {
const [username, setUsername] = useState('');
const [password, setPassword] = useState('');
const navigate = useNavigate(); // Lấy hàm navigate
const handleSubmit = (e) => {
e.preventDefault();
// Xử lý logic đăng nhập...
const success = true; // Giả định đăng nhập thành công
if (success) {
// Điều hướng đến trang dashboard sau khi đăng nhập thành công
navigate('/dashboard');
// navigate('/dashboard', { replace: true }); // Có thể dùng replace để thay thế lịch sử
// navigate(-1); // Điều hướng về trang trước đó
} else {
// Hiển thị thông báo lỗi
}
};
return (
<form onSubmit={handleSubmit}>
{/* Các input cho username và password */}
<button type="submit">Đăng Nhập</button>
</form>
);
}
Hàm navigate
có thể nhận một đường dẫn chuỗi hoặc một số (để điều hướng tiến/lùi trong lịch sử) và có thể nhận thêm một object tùy chọn cho các cài đặt nâng cao như replace
.
Các Hooks Hữu ích Khác
React Router v6 cung cấp một bộ Hooks mạnh mẽ giúp bạn tương tác với trạng thái định tuyến:
useLocation()
: Trả về objectlocation
hiện tại, chứa thông tin về đường dẫn (pathname), query string (search), hash, state,… Rất hữu ích khi bạn cần biết người dùng đang ở đâu hoặc lấy query parameter.useSearchParams()
: Một Hook mới trong v6 giúp làm việc với query string dễ dàng hơn (ví dụ:/products?category=electronics&sort=price
). Nó trả về một mảng[searchParams, setSearchParams]
tương tự nhưuseState
.
import { useLocation, useSearchParams } from 'react-router-dom';
function ProductListPage() {
let location = useLocation();
let [searchParams, setSearchParams] = useSearchParams();
console.log(location.pathname); // Ví dụ: /products
console.log(location.search); // Ví dụ: ?category=electronics&sort=price
let category = searchParams.get('category');
let sort = searchParams.get('sort');
console.log("Category:", category); // electronics
console.log("Sort:", sort); // price
// Cập nhật query parameter
const handleSortChange = (newSort) => {
searchParams.set('sort', newSort);
setSearchParams(searchParams); // Cập nhật URL
}
return (
<div>
<h2>Danh sách Sản phẩm</h2>
<p>Lọc theo danh mục: {category || 'Tất cả'}</p>
<p>Sắp xếp theo: {sort || 'Mặc định'}</p>
<button onClick={() => handleSortChange('name')}>Sắp xếp theo Tên</button>
{/* Hiển thị danh sách sản phẩm dựa trên category và sort */}
</div>
);
}
Việc sử dụng các Hook này giúp logic liên quan đến định tuyến trong Component function trở nên gọn gàng và dễ đọc hơn.
Bảo vệ Route (Protected Routes)
Trong các ứng dụng thực tế, bạn thường có các trang yêu cầu người dùng phải đăng nhập mới có thể truy cập (ví dụ: trang quản lý hồ sơ, trang admin). React Router không cung cấp sẵn một Component <PrivateRoute>
như các phiên bản cũ hoặc các thư viện khác, nhưng bạn có thể dễ dàng xây dựng logic này sử dụng các Component và Hooks của v6.
Cách tiếp cận phổ biến là tạo một Component wrapper kiểm tra trạng thái đăng nhập và sử dụng Component <Navigate>
(hoặc Hook useNavigate
) để chuyển hướng người dùng nếu họ chưa đăng nhập.
// src/components/ProtectedRoute.js
import React from 'react';
import { Navigate, Outlet } from 'react-router-dom';
// Giả định bạn có một hook hoặc context để kiểm tra trạng thái đăng nhập
// import { useAuth } from '../contexts/AuthContext';
function ProtectedRoute({ children }) {
// const { isAuthenticated } = useAuth(); // Lấy trạng thái đăng nhập thực tế
const isAuthenticated = true; // Thay thế bằng logic kiểm tra đăng nhập thật
if (!isAuthenticated) {
// Nếu chưa đăng nhập, chuyển hướng về trang login
// state={{ from: location }} giúp sau khi login thành công có thể quay lại trang cũ
return <Navigate to="/login" state={{ from: location }} replace />;
}
// Nếu đã đăng nhập, render nội dung của route con
// return children ? children : <Outlet />; // Render children nếu được truyền vào, hoặc Outlet
return <Outlet />; // Hoặc chỉ cần dùng Outlet nếu dùng nested route
}
export default ProtectedRoute;
Sau đó, bạn sử dụng ProtectedRoute
trong cấu hình route của mình:
// src/App.js (trong <Routes>...)
// ... các import khác ...
// import ProtectedRoute from './components/ProtectedRoute';
import Dashboard from './pages/Dashboard';
import Settings from './pages/Settings';
function App() {
return (
<BrowserRouter>
<Routes>
{/* Các Public Routes */}
<Route path="/" element={<Layout />} >
<Route index element={<HomePage />} />
<Route path="about" element={<AboutPage />} />
<Route path="contact" element={<ContactPage />} />
<Route path="login" element={<LoginPage />} /> {/* Giả định có LoginPage */}
{/* Protected Routes - Lồng các route cần bảo vệ vào đây */}
<Route element={<ProtectedRoute />}>
<Route path="dashboard" element={<Dashboard />} />
<Route path="settings" element={<Settings />} />
<Route path="users/:userId" element={<UserProfile />} /> {/* Cũng cần login để xem hồ sơ? */}
</Route>
{/* Route 404 */}
<Route path="*" element={<NotFoundPage />} />
</Route>
</Routes>
</BrowserRouter>
);
}
Bằng cách này, bất kỳ ai cố gắng truy cập /dashboard
hoặc /settings
mà chưa đăng nhập sẽ bị chuyển hướng về trang /login
.
React Router v6 so với các phiên bản trước
React Router v6 mang đến nhiều thay đổi đáng kể so với v5, chủ yếu nhằm mục đích đơn giản hóa API, cải thiện hiệu suất và hỗ trợ tốt hơn cho các mô hình phát triển hiện đại như Hooks và nested routing. Dưới đây là bảng tóm tắt một số khác biệt chính:
Tính năng | React Router v5 | React Router v6 | Lý do thay đổi/Lợi ích |
---|---|---|---|
Wrapper cho Route Definitions | <Switch> |
<Routes> |
<Routes> sử dụng thuật toán so khớp tốt hơn, hỗ trợ nested routes tốt hơn, và luôn chọn route phù hợp nhất. |
Cách khai báo Component cho Route | component , render props |
element prop (nhận JSX element) |
Phù hợp với cách React render Component, dễ dàng truyền props, hiệu suất tốt hơn trong một số trường hợp. |
Xử lý khớp chính xác (Exact Matching) | Cần exact prop trên <Route> |
Không cần exact khi sử dụng <Routes> . Thuật toán của <Routes> tự động tìm kiếm kết quả khớp tốt nhất. Riêng với <NavLink> , dùng end prop. |
API đơn giản hơn, trực quan hơn. |
Nested Routes | Thường phải định nghĩa lại path đầy đủ, quản lý rendering thủ công trong Component cha. | Có thể lồng <Route> bên trong <Route> . Sử dụng <Outlet /> trong Component cha để chỉ định nơi render nội dung con. |
Hỗ trợ built-in cho Layouts và cấu trúc route lồng nhau, giúp code gọn gàng và dễ bảo trì hơn. |
Điều hướng bằng Code | Hook useHistory / Object history từ props (Class Component) |
Hook useNavigate |
Tên gọi trực quan hơn, API đơn giản hơn, phù hợp với React Hooks. |
Query Parameters | Truy cập qua location.search , cần thư viện ngoài hoặc xử lý thủ công. |
Hook useSearchParams |
API chuyên biệt, dễ dàng đọc và ghi query parameters. |
Đối với những bạn đã quen thuộc với v5, việc làm quen với v6 có thể mất một chút thời gian để điều chỉnh cú pháp và cách tiếp cận nested routes, nhưng những thay đổi này mang lại lợi ích đáng kể về mặt cấu trúc và hiệu quả.
Kết luận
React Router v6 là một thư viện định tuyến mạnh mẽ và linh hoạt, không thể thiếu khi xây dựng các ứng dụng SPA bằng React. Với cú pháp dựa trên Component, hỗ trợ Hooks toàn diện, và cách xử lý nested routes/layouts trực quan, nó giúp việc quản lý điều hướng trong ứng dụng của bạn trở nên dễ dàng và hiệu quả hơn bao giờ hết.
Chúng ta đã đi qua những khía cạnh quan trọng nhất của React Router v6: từ cài đặt, cấu hình cơ bản với BrowserRouter
, Routes
, Route
; cách điều hướng bằng Link
và NavLink
; xử lý nested routes và layouts bằng <Outlet>
; làm việc với dynamic parameters qua useParams
; điều hướng bằng code với useNavigate
; và sử dụng các Hooks tiện ích như useLocation
, useSearchParams
. Chúng ta cũng đã lướt qua cách xây dựng Protected Routes và điểm qua những thay đổi chính so với v5.
Làm chủ React Router là một bước tiến quan trọng trên Lộ trình học React của bạn. Hãy thực hành bằng cách xây dựng một ứng dụng nhỏ có nhiều trang và thử nghiệm các tính năng định tuyến khác nhau. Việc thực hành sẽ giúp bạn ghi nhớ kiến thức và tự tin hơn khi áp dụng vào các dự án thực tế.
Cảm ơn các bạn đã theo dõi! Hẹn gặp lại trong các bài viết tiếp theo của series “React Roadmap” để cùng khám phá những chủ đề hấp dẫn khác trong thế giới React nhé!