0
0
Lập trình
Admin Team
Admin Teamtechmely

Hướng Dẫn Sử Dụng React Router: Điều Hướng Bằng Nút Nhấn

Đăng vào 7 tháng trước

• 6 phút đọc

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-dom trong các ứng dụng web.
  • replace: true ngă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 Copy
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 Copy
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 Copy
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 Copy
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 Copy
export function NotFound() {
  return <h1>404 — Không Tìm Thấy</h1>;
}

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 Copy
const navigate = useNavigate();
<button onClick={() => {
  // logic kinh doanh …
  navigate("/reports/2025?tab=summary");
}}>
  Xem Tóm Tắt 2025
</button>

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 Copy
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ựcsau khi gửi, hãy ưu tiên API Dữ liệu:

typescript Copy
// 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 Copy
{
  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 Copy
// 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 Copy
navigate("..");        // -> route cha
navigate("../..");     // -> ông bà

Tham số URL và chuỗi truy vấn

typescript Copy
navigate(`/users/${userId}?${new URLSearchParams({ tab: "activity" })}`);

Bảo tồn và hợp nhất tham số tìm kiếm

typescript Copy
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

  1. Nhập từ react-router trong ứng dụng web
    Sử dụng react-router-dom. Gói DOM tái xuất các component mà bạn cần cho trình duyệt.

  2. Thay đổi URLSearchParams trực tiếp
    Sao chép trước khi chỉnh sửa để tránh đọc lỗi:

typescript Copy
setSearchParams(prev => {
  const clone = new URLSearchParams(prev);
  clone.set("page", "1");
  return clone;
});
  1. 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.

  2. Đ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 tra isMounted hoặc hủy bỏ các lời hứa để tránh gọi navigate sau khi gỡ bỏ.


6) Bonus: Hành Động Chuyển Hướng Sau Khi Gửi Biểu Mẫu

typescript Copy
// 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 Copy
{ 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.

Gợi ý câu hỏi phỏng vấn
Không có dữ liệu

Không có dữ liệu

Bài viết được đề xuất
Bài viết cùng tác giả

Bình luận

Chưa có bình luận nào

Chưa có bình luận nào