第 154 章:React Router(Vite 版)
学习目标
- 掌握 React Router 6+ 配置
- 学会声明式路由与嵌套路由
- 理解动态参数与查询参数
- 实现导航守卫
一、为什么需要路由
单页应用(SPA)需要在前端模拟多页面切换。
二、安装
bash
pnpm add react-router-domNext.js 用文件系统路由,不需要这个库。本章针对 Vite + React 项目。
三、基础配置
3.1 main.tsx
tsx
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import { BrowserRouter } from 'react-router-dom';
import App from './App';
createRoot(document.getElementById('root')!).render(
<StrictMode>
<BrowserRouter>
<App />
</BrowserRouter>
</StrictMode>
);3.2 路由表
tsx
// App.tsx
import { Routes, Route, Link } from 'react-router-dom';
import { Home } from './pages/Home';
import { About } from './pages/About';
import { User } from './pages/User';
import { NotFound } from './pages/NotFound';
function App() {
return (
<div>
<nav>
<Link to="/">首页</Link>
<Link to="/about">关于</Link>
<Link to="/user/123">用户</Link>
</nav>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/user/:id" element={<User />} />
<Route path="*" element={<NotFound />} />
</Routes>
</div>
);
}四、动态参数
4.1 useParams
tsx
// pages/User.tsx
import { useParams } from 'react-router-dom';
export function User() {
const { id } = useParams<{ id: string }>();
return <h1>用户 ID:{id}</h1>;
}4.2 useSearchParams(查询参数)
tsx
import { useSearchParams } from 'react-router-dom';
function Products() {
const [params, setParams] = useSearchParams();
const page = params.get('page') ?? '1';
const sort = params.get('sort') ?? 'asc';
return (
<>
<p>第 {page} 页,排序 {sort}</p>
<button onClick={() => setParams({ page: '2' })}>第 2 页</button>
</>
);
}五、嵌套路由
tsx
<Routes>
<Route path="/" element={<Layout />}>
<Route index element={<Home />} />
<Route path="about" element={<About />} />
<Route path="user" element={<UserLayout />}>
<Route index element={<UserList />} />
<Route path=":id" element={<UserDetail />} />
</Route>
</Route>
</Routes>tsx
// Layout.tsx
import { Outlet, Link } from 'react-router-dom';
export function Layout() {
return (
<div>
<nav>
<Link to="/">首页</Link>
<Link to="/about">关于</Link>
<Link to="/user">用户</Link>
</nav>
<main>
<Outlet /> {/* 子路由渲染位置 */}
</main>
</div>
);
}六、编程式导航
tsx
import { useNavigate } from 'react-router-dom';
function LoginForm() {
const navigate = useNavigate();
async function handleLogin() {
await login();
navigate('/dashboard'); // 跳转
navigate(-1); // 后退
navigate('/detail', { replace: true }); // 替换(不留历史)
navigate('/detail', { state: { from: 'login' } }); // 传 state
}
}七、NavLink(高亮)
tsx
import { NavLink } from 'react-router-dom';
<NavLink
to="/about"
className={({ isActive }) => isActive ? 'text-blue-500' : 'text-gray-500'}
>
关于
</NavLink>八、路由守卫(鉴权)
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="/dashboard" element={
<RequireAuth><Dashboard /></RequireAuth>
} />九、懒加载
tsx
import { lazy, Suspense } from 'react';
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>
);
}十、错误边界
tsx
import { useRouteError, isRouteErrorResponse } from 'react-router-dom';
function ErrorPage() {
const error = useRouteError();
if (isRouteErrorResponse(error)) {
return <h1>{error.status} - {error.statusText}</h1>;
}
return <h1>未知错误</h1>;
}
// 在路由中
<Route path="*" element={<ErrorPage />} />十一、Next.js vs Vite Router
| 维度 | Vite + React Router | Next.js App Router |
|---|---|---|
| 配置 | 手动 | 文件系统 |
| SSR | ❌ | ✅ |
| 数据获取 | useEffect + fetch | Server Components |
| 学习成本 | 低 | 中 |
| 适合 | SPA / 后台 | SEO 站 / 全栈 |
十二、本章小结
| 概念 | 关键 |
|---|---|
| BrowserRouter | 顶层包裹 |
| Routes/Route | 路由配置 |
| useParams | 动态参数 |
| useSearchParams | 查询参数 |
| useNavigate | 编程式导航 |
| NavLink | 高亮 |
| 懒加载 | lazy + Suspense |
动手练习
- 配置 4 个路由:首页、关于、用户列表、用户详情
- 用 useParams 接收用户 ID
- 用 useSearchParams 实现分页
- 实现 RequireAuth 守卫
- 路由用 lazy 拆分
推荐阅读
下一章:第 155 章:状态管理 Zustand →