Skip to content
第 16 章 前端 ⏱ 13 分钟阅读

第 16 章:路由 ​

学习目标 ​

  • 理解 SPA 路由的原理
  • 掌握 React Router v6 的 4 个核心 API
  • 学会嵌套路由、动态参数、编程式导航
  • 避开路由的 4 个常见坑

一、SPA 路由原理 ​

单页应用(SPA)只有一个 HTML 文件,通过 History API 或 Hash 切换"路径",JS 监听变化后渲染对应组件,无需重新加载页面。

tsx
// History API
window.history.pushState({}, '', '/about');
window.addEventListener('popstate', () => console.log(location.pathname));

// Hash 模式
window.location.hash = '#/about';
window.addEventListener('hashchange', () => console.log(location.hash));

现代 SPA 用 History API(路径干净 /about,而非 /#/about)。

二、React Router v6 安装 ​

bash
npm install react-router-dom

三、4 个核心 API ​

1. <BrowserRouter> 顶层包裹 ​

tsx
import { BrowserRouter } from 'react-router-dom';

ReactDOM.createRoot(document.getElementById('root')!).render(
  <BrowserRouter>
    <App />
  </BrowserRouter>
);

⚠️ 坑 1:BrowserRouter 必须在最外层,内部才能用路由 hook。

2. <Routes> + <Route> 声明路由 ​

tsx
import { Routes, Route } from 'react-router-dom';

function App() {
  return (
    <Routes>
      <Route path="/" element={<Home />} />
      <Route path="/about" element={<About />} />
      <Route path="*" element={<NotFound />} />
    </Routes>
  );
}

v6 改动:

  • Switch → Routes
  • component / render → element
  • 必须用 <Route element>,不再支持 children 函数
tsx
import { Link } from 'react-router-dom';

function Nav() {
  return (
    <nav>
      <Link to="/">首页</Link>
      <Link to="/about">关于</Link>
    </nav>
  );
}

⚠️ 坑 2:不要用 <a href>,会触发整页刷新,丢失 SPA 状态。

4. useNavigate 编程式导航 ​

tsx
import { useNavigate } from 'react-router-dom';

function Comp() {
  const nav = useNavigate();

  function goLogin() {
    nav('/login');                   // 跳到 /login
    nav('/user', { replace: true }); // 替换历史记录(无后退)
    nav(-1);                         // 后退一页
  }
}

四、动态路由参数 ​

tsx
// 路由声明
<Route path="/user/:id" element={<UserDetail />} />

// 组件读取
import { useParams } from 'react-router-dom';

function UserDetail() {
  const { id } = useParams();
  return <div>用户 ID:{id}</div>;
}

⚠️ 坑 3:useParams() 返回值都是 string | undefined,需要类型断言或手动校验。

五、嵌套路由 ​

tsx
<Routes>
  <Route path="/admin" element={<AdminLayout />}>
    <Route path="users" element={<Users />} />
    <Route path="settings" element={<Settings />} />
    <Route index element={<Dashboard />} />    {/* /admin 默认页 */}
  </Route>
</Routes>

function AdminLayout() {
  return (
    <>
      <Sidebar />
      <main>
        <Outlet />     {/* ⚠️ 子路由在这里渲染 */}
      </main>
    </>
  );
}
tsx
import { NavLink } from 'react-router-dom';

<NavLink
  to="/about"
  className={({ isActive }) => isActive ? 'active' : ''}
>
  关于
</NavLink>

七、路由懒加载 ​

tsx
import { lazy, Suspense } from 'react';
import { Routes, Route } from 'react-router-dom';

const Home = lazy(() => import('./pages/Home'));
const About = lazy(() => import('./pages/About'));

function App() {
  return (
    <Suspense fallback={<Spinner />}>
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/about" element={<About />} />
      </Routes>
    </Suspense>
  );
}

八、search params ​

tsx
import { useSearchParams } from 'react-router-dom';

function Comp() {
  const [params, setParams] = useSearchParams();
  const page = params.get('page') ?? '1';

  return (
    <button onClick={() => setParams({ page: '2' })}>
      当前:{page}
    </button>
  );
}

💡 setParams = 改查询参数(? 后);navigate / <Link> = 改路径(/list → /detail)。列表内翻页筛选用 setParams,跨页面跳用 navigate/Link。

九、路由守卫(认证) ​

tsx
function RequireAuth({ children }: { children: ReactNode }) {
  const { user } = useAuth();
  const location = useLocation();

  if (!user) return <Navigate to="/login" state={{ from: location }} replace />;
  return <>{children}</>;
}

// 使用
<Route path="/profile" element={<RequireAuth><Profile /></RequireAuth>} />

💡 Navigate state = 跳转时塞"小纸条"——URL 看不见(useLocation().state 拿得到),刷新会丢(存内存)。适合临时跳转传值(如跳登录带回原页面),不适合持久化。

statesearch params
URL 显示?❌ 不显示✅ 显示(?xxx=)
刷新还在?❌ 丢了✅ 在
API 拿?useLocation().stateuseSearchParams()
典型场景跳登录带回原路径分页、搜索、分享链接

十、本章小结 ​

要点关键
核心 APIBrowserRouter / Routes / Route / Link
编程式useNavigate(),nav(-1) 后退
动态参数:id + useParams
嵌套路由父用 <Outlet />
高亮NavLink + isActive
守卫自定义组件包 <Navigate replace>
懒加载lazy() + <Suspense>
不要用 <a> 替代 <Link>

动手练习 ​

  1. 基础路由:/、/about、/contact 三个页面 + 导航栏
  2. 动态路由:/user/:id,显示不同 ID 的用户信息(假数据)
  3. 嵌套路由:/admin 下嵌套 users 和 settings
  4. 登录守卫:未登录访问 /profile 跳 /login
  5. 找坑:以下代码哪里错?
    tsx
    <a href="/about" onClick={...}>关于</a>

下一章:第 17 章:状态管理 →

本站基于 VitePress 构建 · 由 StackHub 团队维护