☕ NEW! 完成新手任務即可參加抽獎!LINE 星巴克禮券等你拿,名額有限!        🎉 推廣活動:邀請好友註冊 DevLearn,累積推薦抽 LINE 星巴克禮券! 活動詳情 →        🔥 活動期間 2026/4/1 - 5/31 |已有 0 人參加       
react 中級

🗺️ 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 路由保護模式

💡 大家的想法 · 0

載入中...
💬 即時聊天室 🟢 0 人在線
😀 😎 🤓 💻 🎮 🎸 🔥
➕ 新問題
📋 我的工單
💬 LINE 社群
🔒
需要註冊才能使用此功能
註冊帳號即可解鎖測驗、遊戲、簽到、筆記下載等所有功能,完全免費!
免費註冊