🗺️ React Router:客戶端路由管理
📌 SPA 路由概念
傳統網頁每切換頁面都要跟伺服器要新的 HTML。SPA(Single Page Application)只有一個 HTML,透過 JavaScript 切換畫面。
⚠️ React 本身沒有路由功能! React Router 是獨立的第三方套件,不是 React 內建的。 它透過 JavaScript 監聽 URL 變化,決定顯示哪個元件。
# 安裝 React Router v6
npm install react-router-dom
🛠️ 基本設定(React Router v6)
// main.jsx — 在進入點設定路由
import { BrowserRouter } from 'react-router-dom';
import App from './App';
createRoot(document.getElementById('root')).render(
<BrowserRouter> {/* 包在最外層 */}
<App />
</BrowserRouter>
);
// App.jsx — 定義路由表
import { Routes, Route, Link, Outlet } from 'react-router-dom';
import Home from './pages/Home';
import About from './pages/About';
import NotFound from './pages/NotFound';
function App() {
return (
<div>
{/* 導航列 */}
<nav>
<Link to="/">首頁</Link> {/* Link 取代 <a> 標籤 */}
<Link to="/about">關於</Link>
</nav>
{/* 路由出口 */}
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="*" element={<NotFound />} /> {/* 404 */}
</Routes>
</div>
);
}
💡 Link vs 標籤:
<a href>:會刷新整個頁面(重新載入)<Link to>:只切換元件,不刷新頁面(SPA 體驗)
🔗 動態路由與參數
import { useParams, useSearchParams } from 'react-router-dom';
// 路由定義
<Route path="/users/:userId" element={<UserDetail />} />
// 取得路由參數
function UserDetail() {
const { userId } = useParams(); // 取得 :userId 的值
// 取得查詢參數 ?tab=posts&page=2
const [searchParams, setSearchParams] = useSearchParams();
const tab = searchParams.get('tab') || 'profile';
return (
<div>
<h1>使用者 #{userId}</h1>
<div>
<button onClick={() => setSearchParams({ tab: 'profile' })}>
個人資料
</button>
<button onClick={() => setSearchParams({ tab: 'posts' })}>
貼文
</button>
</div>
{tab === 'profile' ? <Profile /> : <Posts />}
</div>
);
}
🏗️ 巢狀路由(Nested Routes)
// App.jsx
function App() {
return (
<Routes>
<Route path="/" element={<Layout />}> {/* 共用版面 */}
<Route index element={<Home />} /> {/* 預設子路由 */}
<Route path="dashboard" element={<Dashboard />}>
<Route index element={<Overview />} />
<Route path="settings" element={<Settings />} />
<Route path="analytics" element={<Analytics />} />
</Route>
<Route path="*" element={<NotFound />} />
</Route>
</Routes>
);
}
// Layout.jsx — 共用版面配置
function Layout() {
return (
<div>
<Header />
<main>
<Outlet /> {/* 子路由的內容渲染在這裡 */}
</main>
<Footer />
</div>
);
}
// Dashboard.jsx — 巢狀路由的父元件
function Dashboard() {
return (
<div>
<h1>儀表板</h1>
<nav>
<Link to="/dashboard">概覽</Link>
<Link to="/dashboard/settings">設定</Link>
<Link to="/dashboard/analytics">分析</Link>
</nav>
<Outlet /> {/* 子路由渲染處 */}
</div>
);
}
🔒 路由保護(Protected Routes)
import { Navigate, useLocation } from 'react-router-dom';
// 自訂保護元件
function ProtectedRoute({ children }) {
const { user } = useAuth(); // 取得登入狀態(自訂 Hook)
const location = useLocation();
if (!user) {
// 未登入 → 導向登入頁,並記住原本要去的路徑
return <Navigate to="/login" state={{ from: location }} replace />;
}
return children; // 已登入 → 正常顯示
}
// 使用方式
<Routes>
<Route path="/login" element={<Login />} />
<Route path="/dashboard" element={
<ProtectedRoute>
<Dashboard />
</ProtectedRoute>
} />
</Routes>
🧭 程式導航(Programmatic Navigation)
import { useNavigate } from 'react-router-dom';
function LoginForm() {
const navigate = useNavigate();
const handleLogin = async (e) => {
e.preventDefault();
const success = await login(username, password);
if (success) {
navigate('/dashboard'); // 導向儀表板
// navigate(-1); // 回上一頁
// navigate('/dashboard', { replace: true }); // 取代歷史紀錄
}
};
return <form onSubmit={handleLogin}>...</form>;
}
🛠️ 完整範例:多頁面應用
// App.jsx
import { Routes, Route, Link, Navigate } from 'react-router-dom';
import { useState } from 'react';
function App() {
const [isLoggedIn, setIsLoggedIn] = useState(false);
return (
<div>
<nav style={{ display: 'flex', gap: '16px', padding: '16px' }}>
<Link to="/">首頁</Link>
<Link to="/products">商品</Link>
<Link to="/profile">個人資料</Link>
<button onClick={() => setIsLoggedIn(!isLoggedIn)}>
{isLoggedIn ? '登出' : '登入'}
</button>
</nav>
<Routes>
<Route path="/" element={<h1>首頁</h1>} />
<Route path="/products" element={<ProductList />} />
<Route path="/products/:id" element={<ProductDetail />} />
<Route path="/profile" element={
isLoggedIn ? <Profile /> : <Navigate to="/" />
} />
</Routes>
</div>
);
}
✅ 本章重點
| 觀念 | 說明 |
|---|---|
| BrowserRouter | 包在最外層,啟用路由功能 |
| Routes / Route | 定義路徑與元件的對應 |
| Link | SPA 導航,不刷新頁面 |
| useParams | 取得動態路由參數(:id) |
| Outlet | 巢狀路由的渲染出口 |
| Navigate | 程式化重導向 |
| Protected Route | 路由保護模式 |