第 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→Routescomponent/render→element- 必须用
<Route element>,不再支持 children 函数
3. <Link> 导航
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>
</>
);
}六、NavLink 高亮
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拿得到),刷新会丢(存内存)。适合临时跳转传值(如跳登录带回原页面),不适合持久化。
state search params URL 显示? ❌ 不显示 ✅ 显示( ?xxx=)刷新还在? ❌ 丢了 ✅ 在 API 拿? useLocation().stateuseSearchParams()典型场景 跳登录带回原路径 分页、搜索、分享链接
十、本章小结
| 要点 | 关键 |
|---|---|
| 核心 API | BrowserRouter / Routes / Route / Link |
| 编程式 | useNavigate(),nav(-1) 后退 |
| 动态参数 | :id + useParams |
| 嵌套路由 | 父用 <Outlet /> |
| 高亮 | NavLink + isActive |
| 守卫 | 自定义组件包 <Navigate replace> |
| 懒加载 | lazy() + <Suspense> |
| 不要 | 用 <a> 替代 <Link> |
动手练习
- 基础路由:
/、/about、/contact三个页面 + 导航栏 - 动态路由:
/user/:id,显示不同 ID 的用户信息(假数据) - 嵌套路由:
/admin下嵌套users和settings - 登录守卫:未登录访问
/profile跳/login - 找坑:以下代码哪里错?tsx
<a href="/about" onClick={...}>关于</a>
下一章:第 17 章:状态管理 →