15.1 什麼是 React Router?
React Router 是 React 的路由函式庫,讓單頁應用(SPA)可以有多個「頁面」。
npm install react-router-dom
15.2 基本路由設定
import { BrowserRouter, Routes, Route, Link } from 'react-router-dom';
// ============================================================
// 頁面元件
// ============================================================
function HomePage() {
return <h1>首頁</h1>;
}
function AboutPage() {
return <h1>關於我們</h1>;
}
function ContactPage() {
return <h1>聯絡我們</h1>;
}
function NotFoundPage() {
return (
<div style={{ textAlign: 'center', padding: '60px' }}>
<h1 style={{ fontSize: '6rem', margin: 0 }}>404</h1>
<p>找不到頁面</p>
<Link to="/">回到首頁</Link>
</div>
);
}
// ============================================================
// 導航列
// ============================================================
function Navbar() {
return (
<nav style={{
display: 'flex',
gap: '16px',
padding: '16px 24px',
backgroundColor: '#2d3748',
}}>
<Link to="/" style={{ color: 'white', textDecoration: 'none' }}>首頁</Link>
<Link to="/about" style={{ color: 'white', textDecoration: 'none' }}>關於</Link>
<Link to="/contact" style={{ color: 'white', textDecoration: 'none' }}>聯絡</Link>
</nav>
);
}
// ============================================================
// App
// ============================================================
function App() {
return (
<BrowserRouter>
<Navbar />
<Routes>
<Route path="/" element={<HomePage />} />
<Route path="/about" element={<AboutPage />} />
<Route path="/contact" element={<ContactPage />} />
<Route path="*" element={<NotFoundPage />} />
</Routes>
</BrowserRouter>
);
}
export default App;
Link vs NavLink
import { NavLink } from 'react-router-dom';
function Navbar() {
const linkStyle = ({ isActive }) => ({
color: isActive ? '#63b3ed' : 'white',
textDecoration: 'none',
fontWeight: isActive ? 'bold' : 'normal',
borderBottom: isActive ? '2px solid #63b3ed' : 'none',
padding: '8px 0',
});
return (
<nav style={{ display: 'flex', gap: '20px', padding: '16px 24px', backgroundColor: '#2d3748' }}>
<NavLink to="/" style={linkStyle} end>首頁</NavLink>
<NavLink to="/about" style={linkStyle}>關於</NavLink>
<NavLink to="/contact" style={linkStyle}>聯絡</NavLink>
</nav>
);
}
15.3 動態路由與路由參數
import { useParams, Link } from 'react-router-dom';
// 文章列表
function PostList() {
const posts = [
{ id: 1, title: 'React 入門' },
{ id: 2, title: 'Hooks 完全指南' },
{ id: 3, title: 'React Router 教學' },
];
return (
<div>
<h1>文章列表</h1>
{posts.map((post) => (
<div key={post.id}>
<Link to={`/posts/${post.id}`}>{post.title}</Link>
</div>
))}
</div>
);
}
// 文章詳情
function PostDetail() {
const { postId } = useParams(); // 取得 URL 參數
return (
<div>
<h1>文章 #{postId}</h1>
<Link to="/posts">返回列表</Link>
</div>
);
}
// 路由設定
function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/posts" element={<PostList />} />
<Route path="/posts/:postId" element={<PostDetail />} />
</Routes>
</BrowserRouter>
);
}
15.4 巢狀路由 (Nested Routes)
import { BrowserRouter, Routes, Route, Link, Outlet } from 'react-router-dom';
// ============================================================
// Dashboard 佈局
// ============================================================
function DashboardLayout() {
return (
<div style={{ display: 'flex', minHeight: '100vh' }}>
{/* 側邊欄 */}
<nav style={{
width: '200px',
backgroundColor: '#2d3748',
padding: '20px',
}}>
<h2 style={{ color: 'white' }}>Dashboard</h2>
<ul style={{ listStyle: 'none', padding: 0 }}>
<li><Link to="/dashboard" style={{ color: '#a0aec0' }}>總覽</Link></li>
<li><Link to="/dashboard/users" style={{ color: '#a0aec0' }}>使用者</Link></li>
<li><Link to="/dashboard/settings" style={{ color: '#a0aec0' }}>設定</Link></li>
</ul>
</nav>
{/* 內容區(子路由會渲染在 Outlet) */}
<main style={{ flex: 1, padding: '24px' }}>
<Outlet />
</main>
</div>
);
}
function DashboardHome() {
return <h1>儀表板總覽</h1>;
}
function DashboardUsers() {
return <h1>使用者管理</h1>;
}
function DashboardSettings() {
return <h1>系統設定</h1>;
}
// ============================================================
// 路由設定
// ============================================================
function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<HomePage />} />
{/* 巢狀路由 */}
<Route path="/dashboard" element={<DashboardLayout />}>
<Route index element={<DashboardHome />} />
<Route path="users" element={<DashboardUsers />} />
<Route path="settings" element={<DashboardSettings />} />
</Route>
<Route path="*" element={<NotFoundPage />} />
</Routes>
</BrowserRouter>
);
}
15.5 程式化導航 (Programmatic Navigation)
import { useNavigate, useLocation, useSearchParams } from 'react-router-dom';
function LoginPage() {
const navigate = useNavigate();
const location = useLocation();
const handleLogin = () => {
// 登入邏輯...
// 導航到之前嘗試訪問的頁面,或預設到首頁
const from = location.state?.from || '/';
navigate(from, { replace: true });
};
return (
<div>
<h1>登入</h1>
<button onClick={handleLogin}>登入</button>
<button onClick={() => navigate(-1)}>返回上一頁</button>
</div>
);
}
// 查詢參數
function SearchPage() {
const [searchParams, setSearchParams] = useSearchParams();
const query = searchParams.get('q') || '';
const page = Number(searchParams.get('page')) || 1;
const handleSearch = (newQuery) => {
setSearchParams({ q: newQuery, page: 1 });
};
const handlePageChange = (newPage) => {
setSearchParams({ q: query, page: newPage });
};
return (
<div>
<input
value={query}
onChange={(e) => handleSearch(e.target.value)}
placeholder="搜尋..."
/>
<p>搜尋:{query},第 {page} 頁</p>
<button onClick={() => handlePageChange(page - 1)} disabled={page <= 1}>上一頁</button>
<button onClick={() => handlePageChange(page + 1)}>下一頁</button>
</div>
);
}
15.6 路由保護 (Protected Routes)
import { Navigate, useLocation } from 'react-router-dom';
function ProtectedRoute({ children }) {
const isAuthenticated = false; // 替換為實際的認證檢查
const location = useLocation();
if (!isAuthenticated) {
return <Navigate to="/login" state={{ from: location.pathname }} replace />;
}
return children;
}
// 使用
function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<HomePage />} />
<Route path="/login" element={<LoginPage />} />
{/* 受保護的路由 */}
<Route
path="/dashboard"
element={
<ProtectedRoute>
<DashboardLayout />
</ProtectedRoute>
}
>
<Route index element={<DashboardHome />} />
<Route path="settings" element={<DashboardSettings />} />
</Route>
</Routes>
</BrowserRouter>
);
}
15.7 練習題
練習 1:建立一個部落格應用
- 首頁顯示文章列表
- 點擊文章進入詳情頁
/posts/:id - 包含分類頁面
/categories/:category - 有 404 頁面
練習 2:建立一個管理後台
- 登入頁面
- 受保護的 Dashboard 路由
- 巢狀路由(總覽、使用者管理、設定)
- 麵包屑導航
本課重點回顧
BrowserRouter+Routes+Route定義路由Link/NavLink用於導航useParams取得動態路由參數Outlet渲染巢狀路由的子元件useNavigate程式化導航useSearchParams管理查詢參數- 用
Navigate元件實現路由保護