Hướng Dẫn Sử Dụng React Router: Điều Hướng Bằng Nút Nhấn
Lưu ý: Trong các ứng dụng web, hãy nhập từ react-router-dom, không phải react-router.
Ví dụ: import { createBrowserRouter, RouterProvider, Link, redirect } from "react-router-dom";
Hướng dẫn này sẽ chỉ cho bạn cách điều hướng đến một route khác khi một nút được nhấn, sử dụng React Router v6.4+ với các API Dữ liệu. Chúng ta sẽ đề cập đến các mẫu sẵn sàng cho sản xuất mà bạn thực sự muốn trong một ứng dụng thực tế: useNavigate, các liên kết tương đương, chuyển hướng loader/action, truyền trạng thái, điều hướng tương đối và những cạm bẫy thường gặp.
Tóm Tắt
- Sử dụng
useNavigate()trong một component để điều hướng khi nhấn. - Ưu tiên
redirect()trong loaders/actions cho các chuyển hướng trước khi render (không có hiện tượng nhấp nháy). - Giữ các import từ
react-router-domtrong các ứng dụng web. replace: truengăn chặn ô nhiễm lịch sử (tuyệt vời cho các luồng xác thực).
1) Ví Dụ Hoạt Động Tối Thiểu (TypeScript)
main.tsx
typescript
import React from "react";
import ReactDOM from "react-dom/client";
import { createBrowserRouter, RouterProvider } from "react-router-dom";
import { AppLayout } from "./routes/AppLayout";
import { Home } from "./routes/Home";
import { Dashboard } from "./routes/Dashboard";
import { NotFound } from "./routes/NotFound";
const router = createBrowserRouter([
{
path: "/",
element: <AppLayout />,
children: [
{ index: true, element: <Home /> },
{ path: "dashboard", element: <Dashboard /> },
{ path: "*", element: <NotFound /> },
],
},
]);
ReactDOM.createRoot(document.getElementById("root")!).render(
<React.StrictMode>
<RouterProvider router={router} />
</React.StrictMode>
);
routes/AppLayout.tsx
typescript
import { Outlet, Link } from "react-router-dom";
export function AppLayout() {
return (
<div style={{ padding: 24, fontFamily: "ui-sans-serif, system-ui" }}>
<header style={{ display: "flex", gap: 12, marginBottom: 16 }}>
<Link to="/">Trang Chủ</Link>
<Link to="/dashboard">Bảng Điều Khiển</Link>
</header>
<Outlet />
</div>
);
}
routes/Home.tsx — điều hướng khi nhấn nút
typescript
import { useNavigate } from "react-router-dom";
export function Home() {
const navigate = useNavigate();
const goToDashboard = () => {
// điều hướng push (nút quay lại sẽ trở về đây)
navigate("/dashboard", {
state: { from: "home", flash: "Chào mừng đến với bảng điều khiển của bạn!" },
});
};
return (
<section>
<h1>Trang Chủ</h1>
<p>Nhấn nút để điều hướng một cách lập trình.</p>
<button onClick={goToDashboard}>Đi đến Bảng Điều Khiển</button>
</section>
);
}
routes/Dashboard.tsx — đọc trạng thái điều hướng
typescript
import { useLocation, useNavigate } from "react-router-dom";
export function Dashboard() {
const location = useLocation();
const navigate = useNavigate();
const flash = (location.state as any)?.flash as string | undefined;
return (
<section>
<h1>Bảng Điều Khiển</h1>
{flash && <p style={{ color: "rebeccapurple" }}>{flash}</p>}
<button onClick={() => navigate(-1)}>⬅ Quay Lại</button>
<button onClick={() => navigate("/", { replace: true })}>
Về Trang Chủ (thay thế)
</button>
</section>
);
}
routes/NotFound.tsx
typescript
export function NotFound() {
return <h1>404 — Không Tìm Thấy</h1>;
}
2) Nút vs <Link> vs Hành Động Biểu Mẫu
A) Nút + useNavigate (dựa trên sự kiện)
Sử dụng khi nhấn vào một nút cần điều hướng sau một số logic (ví dụ: kiểm tra quyền truy cập, phân tích, lưu bất đồng bộ).
typescript
const navigate = useNavigate();
<button onClick={() => {
// logic kinh doanh …
navigate("/reports/2025?tab=summary");
}}>
Xem Tóm Tắt 2025
</button>
B) Điều hướng giống như liên kết (<Link>)
Nếu bạn chỉ cần một phần tử điều hướng mà không có logic bổ sung, hãy ưu tiên <Link> để cải thiện khả năng tiếp cận + hành vi prefetch.
typescript
import { Link } from "react-router-dom";
<Link to="/settings?section=profile">Cài Đặt Hồ Sơ</Link>
C) Chuyển hướng trước khi render với actions/loaders (không có hiện tượng nhấp nháy)
Đối với các chuyển hướng xác thực và sau khi gửi, hãy ưu tiên API Dữ liệu:
typescript
// routes/protected.tsx
import { redirect, useRouteError } from "react-router-dom";
export async function protectedLoader() {
const isAuthed = await getSession(); // kiểm tra xác thực của bạn
if (!isAuthed) return redirect("/login?reason=unauthorized");
return null;
}
export function ProtectedPage() { return <h1>Nội Dung Đã Xác Thực</h1>; }
export function ProtectedErrorBoundary() {
const err = useRouteError();
return <p>Có lỗi xảy ra: {String(err)}</p>;
}
Cấu hình route:
typescript
{
path: "protected",
loader: protectedLoader,
element: <ProtectedPage />,
errorElement: <ProtectedErrorBoundary />,
}
Điều này không bao giờ render component được bảo vệ khi không được phép; người dùng sẽ trực tiếp đến /login mà không có nội dung nào xuất hiện.
3) Điều Hướng Tương Đối & Động (Mẹo Chuyên Nghiệp)
Đường dẫn tương đối
Bên trong các route lồng nhau, các đường dẫn tương đối giúp mọi thứ trở nên tách biệt:
typescript
// Từ /projects/:id
const navigate = useNavigate();
<button onClick={() => navigate("settings")}>Cài Đặt Dự Án</button>
// điều hướng đến /projects/:id/settings
Đi lên một đoạn:
typescript
navigate(".."); // -> route cha
navigate("../.."); // -> ông bà
Tham số URL và chuỗi truy vấn
typescript
navigate(`/users/${userId}?${new URLSearchParams({ tab: "activity" })}`);
Bảo tồn và hợp nhất tham số tìm kiếm
typescript
import { useSearchParams } from "react-router-dom";
const [params] = useSearchParams();
const navigate = useNavigate();
const next = new URLSearchParams(params);
next.set("page", String(Number(params.get("page") ?? 1) + 1));
navigate({ pathname: "/inbox", search: next.toString() });
4) replace so với push mặc định
- Push (mặc định):
navigate("/path")thêm một mục vào lịch sử (Quay lại sẽ trở về trang trước). - Thay thế:
navigate("/path", { replace: true })thay thế mục hiện tại → Sử dụng cho sau khi đăng nhập, sau khi đăng xuất, hoặc khi trang trước không nên được truy cập lại.
5) Những Cạm Bẫy Thường Gặp & Cách Tránh Chúng
-
Nhập từ
react-routertrong ứng dụng web
Sử dụngreact-router-dom. Gói DOM tái xuất các component mà bạn cần cho trình duyệt. -
Thay đổi
URLSearchParamstrực tiếp
Sao chép trước khi chỉnh sửa để tránh đọc lỗi:
typescript
setSearchParams(prev => {
const clone = new URLSearchParams(prev);
clone.set("page", "1");
return clone;
});
-
Chuyển hướng trong các component gây nhấp nháy
Nếu một chuyển hướng là điều kiện và đã biết trước khi render, hãy sử dụng một chuyển hướng loader (redirect()), không phải<Navigate />trong component. -
Điều hướng trong các component không được gắn
Bọc các handler bất đồng bộ và kiểm traisMountedhoặc hủy bỏ các lời hứa để tránh gọinavigatesau khi gỡ bỏ.
6) Bonus: Hành Động Chuyển Hướng Sau Khi Gửi Biểu Mẫu
typescript
// routes/contact.tsx
import { ActionFunctionArgs, Form, redirect, useActionData } from "react-router-dom";
export async function contactAction({ request }: ActionFunctionArgs) {
const form = await request.formData();
await saveMessage({ email: String(form.get("email")), body: String(form.get("body")) });
return redirect("/thanks");
}
export function Contact() {
const data = useActionData() as { error?: string } | undefined;
return (
<Form method="post">
<input name="email" type="email" required />
<textarea name="body" required />
<button type="submit">Gửi</button>
{data?.error && <p role="alert">{data.error}</p>}
</Form>
);
}
Cấu hình route:
typescript
{ path: "contact", action: contactAction, element: <Contact /> }
Kết Luận
- Đối với điều hướng bằng nút nhấn, hãy sử dụng
useNavigate(). - Đối với logic trước khi render (xác thực, sau khi gửi), hãy sử dụng
redirect()trong loaders/actions. - Luôn nhập các bindings trình duyệt từ
react-router-dom. - Ưu tiên điều hướng tương đối để giảm sự phụ thuộc trong các route lồng nhau.
Cấu hình này mở rộng sạch sẽ từ các demo nhỏ đến các ứng dụng sản xuất.
✍️ Viết bởi: Cristian Sifuentes --- Nhà phát triển Full-stack & Người yêu thích AI, đam mê xây dựng các kiến trúc có thể mở rộng và giảng dạy các đội ngũ phát triển cách phát triển trong thời đại AI.