创作者: Yardon | GitHub: github.com/YardonYan | 版本: v1.0
单页应用的 URL 问题
传统的多页网站,每个页面有自己的 URL。你在浏览器地址栏输入 /about,服务器就返回 about.html,每一页都是完整独立的一次往返。
React 做的单页应用(SPA)不一样------浏览器加载一个 HTML 文件之后,所有的页面切换都在前端完成,服务器只负责提供 JSON 数据。
这就带来一个问题:URL 怎么变?
用户期望的行为是------点击导航、刷新页面、前进后退都能正常工作。这意味着 React 必须"接管"浏览器的 URL,在不刷新页面的前提下改变地址栏的内容。
React Router 就是做这件事的。它的核心思想很简单:
把 URL 当成一个状态,当它变化时,渲染对应的组件。
React Router v6 快速上手
安装与基础配置
bash
npm install react-router-dom
jsx
// main.jsx
import { BrowserRouter } from 'react-router-dom';
import App from './App';
createRoot(document.getElementById('root')).render(
<BrowserRouter>
<App />
</BrowserRouter>
);
jsx
// App.jsx
import { Routes, Route } from 'react-router-dom';
function App() {
return (
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/blog" element={<Blog />} />
<Route path="*" element={<NotFound />} />
</Routes>
);
}
Link 和 NavLink
jsx
import { Link, NavLink } from 'react-router-dom';
function Navbar() {
return (
<nav>
{/* 基础链接 */}
<Link to="/">首页</Link>
{/* NavLink 会自动给当前页面高亮 */}
<NavLink
to="/blog"
className={({ isActive }) => isActive ? 'active' : ''}
>
博客
</NavLink>
</nav>
);
}
NavLink 是 Link 的增强版------它知道自己是不是当前页,可以自动应用激活样式。
编程式导航
有时候需要在代码里控制跳转(比如表单提交成功后跳转到新页面):
jsx
import { useNavigate } from 'react-router-dom';
function LoginForm() {
const navigate = useNavigate();
async function handleSubmit(e) {
e.preventDefault();
const result = await login(username, password);
if (result.success) {
navigate('/dashboard'); // 成功后跳转
}
}
}
路由嵌套与 Outlet
一个常见的布局模式:顶部导航栏、侧边栏是固定的,中间的内容区域跟随路由变化。
┌─────────────────────────────┐
│ Navbar (固定) │
├────────┬────────────────────┤
│Sidebar │ 内容区域 (变化) │
│ (固定) │ │
│ │ │
└────────┴────────────────────┘
jsx
function App() {
return (
<Routes>
<Route path="/" element={<Layout />}>
{/* 这些路由的内容会渲染到 Layout 的 Outlet 位置 */}
<Route index element={<Home />} />
<Route path="dashboard" element={<Dashboard />} />
<Route path="settings" element={<Settings />} />
</Route>
</Routes>
);
}
function Layout() {
return (
<div>
<Navbar />
<div className="main">
<Sidebar />
<div className="content">
<Outlet /> {/* 子路由的内容渲染在这里 */}
</div>
</div>
</div>
);
}
<Outlet /> 就是路由的「插槽」------子路由匹配到哪个组件,它就渲染哪个组件。而 Layout 组件本身保持不变(导航栏、侧边栏不用重新渲染)。这个特性在搭建复杂布局时非常关键。
动态路由与 URL 参数
博客文章页面,每篇文章有一个唯一 ID。URL 大概是 /blog/123,其中 123 是动态的。
jsx
<Route path="/blog/:id" element={<BlogPost />} />
jsx
import { useParams } from 'react-router-dom';
function BlogPost() {
const { id } = useParams(); // id = "123"
const [post, setPost] = useState(null);
useEffect(() => {
fetch(`/api/posts/${id}`)
.then(res => res.json())
.then(setPost);
}, [id]);
if (!post) return <Loading />;
return <PostDetail post={post} />;
}
导航守卫与重定向
保护需要登录的页面
jsx
function ProtectedRoute({ children }) {
const isLoggedIn = useAuthStore(s => s.isLoggedIn);
if (!isLoggedIn) {
// 未登录 → 跳到登录页,并记住想去的页面
return <Navigate to="/login" replace />;
}
return children;
}
// 使用
<Route path="/dashboard" element={
<ProtectedRoute>
<Dashboard />
</ProtectedRoute>
} />
旧路径重定向到新路径
jsx
// 旧版路径自动跳转
<Route path="/old-about" element={<Navigate to="/about" replace />} />
查询参数与 useSearchParams
博客列表页的翻页和筛选一般用查询参数实现:/blog?page=2&tag=react
jsx
import { useSearchParams } from 'react-router-dom';
function BlogList() {
const [searchParams, setSearchParams] = useSearchParams();
const page = Number(searchParams.get('page')) || 1;
const tag = searchParams.get('tag') || '';
function goToPage(n) {
setSearchParams({ page: n, tag }); // 保留其他参数
}
return (
<div>
{/* 翻页按钮 */}
<button onClick={() => goToPage(page - 1)} disabled={page === 1}>上一页</button>
<span>第 {page} 页</span>
<button onClick={() => goToPage(page + 1)}>下一页</button>
</div>
);
}
懒加载与代码分割
当应用变大后,所有页面打包进一个 JS 文件会导致首屏加载很慢。React Router 配合 lazy 实现按需加载:
jsx
import { lazy, Suspense } from 'react';
// 每个页面单独打包,访问时才加载
const Home = lazy(() => import('./pages/Home'));
const Dashboard = lazy(() => import('./pages/Dashboard'));
const Settings = lazy(() => import('./pages/Settings'));
function App() {
return (
<Suspense fallback={<LoadingSpinner />}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/settings" element={<Settings />} />
</Routes>
</Suspense>
);
}
Suspense 在组件加载过程中显示一个 fallback(加载动画),让用户知道"页面正在加载",而不是看一片空白。
本章小结
| 概念 | 一句话总结 |
|---|---|
| BrowserRouter | 包装整个应用,启用前端路由 |
| Routes/Route | 声明式定义 URL 到组件的映射 |
| Link/NavLink | 无刷新的页面跳转,NavLink 自带激活判断 |
| Outlet | 嵌套路由的插槽,布局组件渲染子路由内容 |
| useParams | 读取动态路由参数 /blog/:id |
| Navigate | 声明式重定向 |
| useSearchParams | 读写查询参数 /blog?page=2 |
| lazy + Suspense | 按需加载页面,减小首屏包体积 |
路由是现代前端框架的基石。掌握这些概念后,你就拥有了搭建完整多页 SPA 的能力。下一章,我们聚焦 性能优化------如何让 React 应用更快、更流畅。
📌 创作者: Yardon | 🏠 个人网站: GlimmerAI.top
📖 本章是「React 从入门到生产 」系列的第 6 章。上一章:状态管理选型 | 下一章:性能优化
🌟 如果你觉得有帮助,欢迎访问 GlimmerAI.top 查看我的更多作品。欢迎大家来观看!