測驗:React Router 導航守衛與路由保護實戰
共 5 題,點選答案後會立即顯示結果
1. 什麼是路由守衛(Route Guard)的主要功能?
2. Navigate 元件和 useNavigate Hook 的主要差異是什麼?
3. 在 <Navigate to="/login" replace /> 中,replace 屬性的作用是什麼?
4. 使用 useLocation Hook 可以取得哪些資訊?
5. 在 PrivateRoute 元件中,為什麼要使用 state={{ from: location }} 傳遞資訊?
一句話說明
在路由切換前檢查使用者是否有權限,沒有就導去登入頁。
前置知識
- 第 4 篇:巢狀路由與 Outlet 佈局
什麼是路由守衛?
路由守衛(Route Guard)就是在使用者進入某個頁面之前,先檢查他有沒有權限。
使用者想去 /dashboard
↓
有登入嗎?
↙ ↘
有 沒有
↓ ↓
進入頁面 導去 /login
React Router 沒有內建路由守衛,但我們可以用元件組合的方式實作。
Navigate 元件:宣告式重導向
最小範例
import { Navigate } from "react-router-dom";
function Dashboard() {
const isLoggedIn = false;
if (!isLoggedIn) {
return <Navigate to="/login" />; // 沒登入就導去 /login
}
return <h1>Dashboard</h1>;
}
Code language: JavaScript (javascript)逐行翻譯
import { Navigate } from "react-router-dom"; // 引入重導向元件
if (!isLoggedIn) {
return <Navigate to="/login" />; // 如果沒登入,回傳 Navigate 元件
}
// ↑ Navigate 一被渲染就會自動跳轉到 to 指定的路徑
Code language: JavaScript (javascript)常見變化
變化 1:加上 replace
<Navigate to="/login" replace />
Code language: HTML, XML (xml)翻譯:導向 /login,但不會在瀏覽紀錄留下一筆(使用者按上一頁不會回來)
變化 2:帶狀態傳遞
<Navigate to="/login" state={{ from: location }} />
Code language: HTML, XML (xml)翻譯:導向 /login,同時把「從哪裡來」的資訊帶過去
useNavigate Hook:程式化導航
最小範例
import { useNavigate } from "react-router-dom";
function LoginButton() {
const navigate = useNavigate();
function handleLogin() {
// 登入成功後...
navigate("/dashboard"); // 用程式導向 /dashboard
}
return <button onClick={handleLogin}>登入</button>;
}
Code language: JavaScript (javascript)逐行翻譯
const navigate = useNavigate(); // 取得導航函式
navigate("/dashboard"); // 呼叫它就會跳轉
Code language: JavaScript (javascript)Navigate vs useNavigate
| Navigate | useNavigate |
|---|---|
| 是元件,寫在 JSX 裡 | 是 Hook,寫在函式裡 |
| 渲染時立即跳轉 | 呼叫時才跳轉 |
| 適合條件判斷 | 適合事件處理 |
// Navigate:條件渲染時用
if (!user) return <Navigate to="/login" />;
// useNavigate:按鈕點擊時用
onClick={() => navigate("/dashboard")}
Code language: JavaScript (javascript)常見用法
const navigate = useNavigate();
// 基本導航
navigate("/users");
// 帶參數
navigate("/users/123");
// 返回上一頁
navigate(-1);
// 前進一頁
navigate(1);
// 導航並取代目前紀錄
navigate("/dashboard", { replace: true });
// 導航並帶狀態
navigate("/dashboard", { state: { from: "login" } });
Code language: JavaScript (javascript)useLocation:取得當前路由資訊
最小範例
import { useLocation } from "react-router-dom";
function CurrentPath() {
const location = useLocation();
return <p>目前在:{location.pathname}</p>;
}
Code language: JavaScript (javascript)location 物件結構
const location = useLocation();
console.log(location);
// {
// pathname: "/users/123", // 目前路徑
// search: "?sort=name", // 查詢字串
// hash: "#section1", // 錨點
// state: { from: "login" }, // 傳遞的狀態
// key: "abc123" // 唯一識別碼
// }
Code language: JavaScript (javascript)一句話:location 是「你現在在哪裡」的完整資訊。
路由守衛元件設計模式
PrivateRoute 元件
這是最常見的路由保護模式:
import { Navigate, useLocation } from "react-router-dom";
function PrivateRoute({ children }) {
const isLoggedIn = checkAuth(); // 檢查是否登入
const location = useLocation();
if (!isLoggedIn) {
// 沒登入:導去 login,並記住原本要去哪
return <Navigate to="/login" state={{ from: location }} replace />;
}
// 有登入:顯示原本的內容
return children;
}
Code language: JavaScript (javascript)逐行翻譯
function PrivateRoute({ children }) { // children 是被包住的元件
const isLoggedIn = checkAuth(); // 檢查登入狀態
const location = useLocation(); // 取得目前位置
if (!isLoggedIn) {
return <Navigate
to="/login" // 導向登入頁
state={{ from: location }} // 記住「從哪來」
replace // 不留瀏覽紀錄
/>;
}
return children; // 通過檢查,顯示內容
}
Code language: PHP (php)使用方式
// 方法 1:包住單一元件
<Route
path="/dashboard"
element={
<PrivateRoute>
<Dashboard />
</PrivateRoute>
}
/>
// 方法 2:配合 Outlet 保護整組路由
<Route element={<PrivateRoute><Outlet /></PrivateRoute>}>
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/settings" element={<Settings />} />
<Route path="/profile" element={<Profile />} />
</Route>
Code language: PHP (php)完整登入流程範例
1. 建立認證 Context
// AuthContext.jsx
import { createContext, useContext, useState } from "react";
const AuthContext = createContext(null);
export function AuthProvider({ children }) {
const [user, setUser] = useState(null);
function login(userData) {
setUser(userData);
}
function logout() {
setUser(null);
}
return (
<AuthContext.Provider value={{ user, login, logout }}>
{children}
</AuthContext.Provider>
);
}
export function useAuth() {
return useContext(AuthContext);
}
Code language: JavaScript (javascript)2. 建立 PrivateRoute 元件
// PrivateRoute.jsx
import { Navigate, useLocation } from "react-router-dom";
import { useAuth } from "./AuthContext";
export function PrivateRoute({ children }) {
const { user } = useAuth();
const location = useLocation();
if (!user) {
return <Navigate to="/login" state={{ from: location }} replace />;
}
return children;
}
Code language: JavaScript (javascript)3. 建立登入頁面(含返回原頁面)
// LoginPage.jsx
import { useState } from "react";
import { useNavigate, useLocation } from "react-router-dom";
import { useAuth } from "./AuthContext";
export function LoginPage() {
const [email, setEmail] = useState("");
const { login } = useAuth();
const navigate = useNavigate();
const location = useLocation();
// 取得「從哪裡來」,沒有就預設去首頁
const from = location.state?.from?.pathname || "/";
function handleSubmit(e) {
e.preventDefault();
// 登入成功
login({ email });
// 導回原本要去的頁面
navigate(from, { replace: true });
}
return (
<form onSubmit={handleSubmit}>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="Email"
/>
<button type="submit">登入</button>
</form>
);
}
Code language: JavaScript (javascript)4. 設定路由
// App.jsx
import { BrowserRouter, Routes, Route, Outlet } from "react-router-dom";
import { AuthProvider } from "./AuthContext";
import { PrivateRoute } from "./PrivateRoute";
import { LoginPage } from "./LoginPage";
import { Dashboard } from "./Dashboard";
import { Settings } from "./Settings";
function App() {
return (
<AuthProvider>
<BrowserRouter>
<Routes>
{/* 公開路由 */}
<Route path="/" element={<Home />} />
<Route path="/login" element={<LoginPage />} />
{/* 受保護的路由 */}
<Route element={<PrivateRoute><Outlet /></PrivateRoute>}>
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/settings" element={<Settings />} />
</Route>
</Routes>
</BrowserRouter>
</AuthProvider>
);
}
Code language: JavaScript (javascript)流程圖解
使用者點擊 /dashboard
↓
PrivateRoute 檢查
↓
user 存在嗎?
↙ ↘
存在 不存在
↓ ↓
顯示 Dashboard Navigate to /login
帶著 state: { from: "/dashboard" }
↓
使用者在登入頁輸入帳號
↓
登入成功
↓
navigate(from) → 回到 /dashboard
Code language: JavaScript (javascript)常見變化
變化 1:角色權限檢查
function RoleRoute({ children, allowedRoles }) {
const { user } = useAuth();
const location = useLocation();
if (!user) {
return <Navigate to="/login" state={{ from: location }} replace />;
}
if (!allowedRoles.includes(user.role)) {
return <Navigate to="/unauthorized" replace />;
}
return children;
}
// 使用
<Route element={<RoleRoute allowedRoles={["admin"]}><Outlet /></RoleRoute>}>
<Route path="/admin" element={<AdminPanel />} />
</Route>
Code language: PHP (php)變化 2:Loading 狀態處理
function PrivateRoute({ children }) {
const { user, loading } = useAuth();
const location = useLocation();
// 還在檢查登入狀態
if (loading) {
return <div>Loading...</div>;
}
if (!user) {
return <Navigate to="/login" state={{ from: location }} replace />;
}
return children;
}
Code language: JavaScript (javascript)變化 3:登出後導向
function LogoutButton() {
const { logout } = useAuth();
const navigate = useNavigate();
function handleLogout() {
logout();
navigate("/login", { replace: true });
}
return <button onClick={handleLogout}>登出</button>;
}
Code language: JavaScript (javascript)Vibe Coder 檢查點
看到路由守衛相關代碼時確認:
- [ ] PrivateRoute 有正確檢查登入狀態嗎?
- [ ] Navigate 有加
replace嗎?(避免使用者按上一頁繞過) - [ ] 有用
state記住「從哪來」嗎?(登入後才能返回) - [ ] LoginPage 有處理
location.state?.from嗎? - [ ] 有處理 loading 狀態嗎?(避免閃一下再跳轉)
核心概念翻譯表
| 你會看到 | 意思 |
|---|---|
<Navigate to="/login" /> |
立即重導向到 /login |
<Navigate replace /> |
重導向但不留紀錄 |
state={{ from: location }} |
把「從哪來」的資訊帶過去 |
const navigate = useNavigate() |
取得程式化導航的函式 |
navigate(-1) |
返回上一頁 |
navigate("/path", { replace: true }) |
導向並取代目前紀錄 |
const location = useLocation() |
取得目前的路由資訊 |
location.state?.from |
取得傳過來的「從哪來」資訊 |
<PrivateRoute>{children}</PrivateRoute> |
包住需要保護的內容 |
系列總結
恭喜你完成 React Router 系列!讓我們回顧一下:
| 篇章 | 學到的重點 |
|---|---|
| #01 | BrowserRouter、Routes、Route 基本設定 |
| #02 | Link、NavLink 導航,useParams 取得動態參數 |
| #03 | useSearchParams 處理查詢字串 |
| #04 | 巢狀路由、Outlet 佈局 |
| #05 | Navigate、useNavigate、useLocation、路由守衛 |
現在你應該能看懂 AI 生成的 React Router 代碼了!
延伸:知道就好
這些進階功能遇到再查:
- loader/action:React Router 6.4+ 的資料載入方式
- defer/Await:延遲載入與 Suspense 整合
- errorElement:路由層級的錯誤邊界
- lazy:動態載入路由元件
進階測驗:React Router 導航守衛與路由保護實戰
測驗目標:驗證你是否能在實際情境中應用所學。
共 5 題,包含情境題與錯誤診斷題。
共 5 題,包含情境題與錯誤診斷題。
1. 你正在開發一個電商網站,需要保護 /checkout 結帳頁面只讓已登入的使用者存取。同時,你希望未登入使用者被導向登入頁後,登入成功能自動返回結帳頁面。最佳做法是? 情境題
2. 小明實作了一個 PrivateRoute,但使用者反映按瀏覽器的「上一頁」按鈕可以繞過登入檢查回到受保護頁面。問題出在哪裡? 錯誤診斷
function PrivateRoute({ children }) {
const { user } = useAuth();
if (!user) {
return <Navigate to=”/login” />;
}
return children;
}
3. 你的網站有管理員後台(/admin),需要檢查使用者是否登入且角色為 admin。你應該如何設計 RoleRoute 元件? 情境題
4. 小華的登入頁面在登入成功後總是導向首頁,無法返回使用者原本想去的頁面。以下程式碼有什麼問題? 錯誤診斷
function LoginPage() {
const { login } = useAuth();
const navigate = useNavigate();
function handleSubmit(e) {
e.preventDefault();
login({ email });
navigate(“/”); // 登入後導向首頁
}
return <form onSubmit={handleSubmit}>…</form>;
}