React Router 完全指南:从入门到实战,一文搞懂路由核心技巧
你是否还在为路由配置发愁?是否分不清
BrowserRouter和HashRouter的区别?本文将带你从零开始,系统掌握 React Router 的核心用法,包括懒加载、动态路由、嵌套路由、鉴权拦截等实战必备技能,并附上 Vue 与 React 路由的对比思考。
前言:为什么需要前端路由?
在传统的 Web 开发中,每次页面跳转都需要向服务器请求新的 HTML,导致页面刷新、体验卡顿。而随着 Ajax 技术的普及,我们可以在不刷新页面的情况下与服务器交换数据并局部更新网页。
这催生了 SPA(Single Page Application,单页应用) 的诞生------整个网站只有一个 HTML 页面,所有页面切换由前端路由控制,内容通过 JavaScript 动态渲染,实现无刷新切换。
SPA 的优点 :体验流畅,类似原生 App。
SPA 的缺点:SEO 不友好(搜索引擎难以抓取动态内容),需配合 SSR(服务端渲染)解决。
关键关系:
- Ajax 是手段:负责异步获取数据,局部更新。
- SPA 是终极形态:依赖前端路由 + Ajax 实现 URL 变化但不刷新页面。
一、路由选型:BrowserRouter vs HashRouter
React Router 提供了两种路由模式,区别如下:
| 模式 | 创建方式 | URL 示例 | 特点 |
|---|---|---|---|
| BrowserRouter | createBrowserRouter |
http://localhost/home |
依赖 History API,URL 干净,符合 RESTful 风格;部署时需要服务端配置回退到 index.html |
| HashRouter | createHashRouter |
http://localhost/#/home |
利用 hash 路由,无需服务端支持,适合静态站点 |
建议 :大多数项目优先使用
BrowserRouter,除非你部署在纯静态服务器上(如 GitHub Pages)。
二、前置准备:项目根路由结构
在 main.jsx(或 index.js)中,先用 <BrowserRouter> 或 <HashRouter> 包裹整个应用:
jsx
// main.jsx
import React from 'react';
import ReactDOM from 'react-dom/client';
import { BrowserRouter } from 'react-router-dom';
import App from './App';
ReactDOM.createRoot(document.getElementById('root')).render(
<BrowserRouter>
<App />
</BrowserRouter>
);
三、核心路由技巧(实战篇)
1. 懒加载(按需加载)------ 提升首屏性能
场景:大型项目首页加载太慢,需要代码分割,只有访问该页面时才加载对应的 JS 文件。
实现方式:
- 使用
React.lazy()定义组件,实现异步加载。 - 使用
<Suspense>包裹路由组件,并指定fallback作为加载中的占位 UI(如骨架屏、Loading 图标)。
jsx
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={<div>页面加载中...</div>}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
</Routes>
</Suspense>
);
}
注意 :
lazy组件必须放在Suspense内部,否则会报错。
2. 动态路由 ------ 处理参数(如 /user/123)
场景:详情页、个人主页等,URL 中携带 ID 或唯一标识。
实现方式:
- 路径中用
:参数名占位。 - 组件内用
useParams()获取参数。
jsx
// 路由配置
<Route path="/user/:id" element={<UserDetail />} />
// UserDetail.jsx
import { useParams } from 'react-router-dom';
function UserDetail() {
const { id } = useParams(); // 假设 URL 是 /user/123,则 id = "123"
useEffect(() => {
fetchUser(id); // 根据 id 请求数据
}, [id]);
return <div>当前用户 ID:{id}</div>;
}
3. 嵌套路由 ------ 构建页面层级结构
场景:后台管理、仪表盘,侧边栏或顶部导航固定,只有内容区域随路由切换。
实现方式:
- 父组件内用
<Outlet />占位,表示子路由渲染的位置。 - 子路由使用相对路径(不需要写父路径前缀)。
jsx
// 路由配置
<Route path="/dashboard" element={<DashboardLayout />}>
<Route path="home" element={<DashboardHome />} /> // 实际路径 /dashboard/home
<Route path="settings" element={<Settings />} /> // 实际路径 /dashboard/settings
</Route>
// DashboardLayout.jsx
import { Outlet, Link } from 'react-router-dom';
function DashboardLayout() {
return (
<div>
<header>侧边栏 / 顶部导航</header>
<main>
<Outlet /> {/* 子路由在这里渲染 */}
</main>
</div>
);
}
4. 重定向 ------ 旧路径跳转到新路径
场景:网站改版、入口页跳转、用户登录后跳转。
实现方式 :使用 <Navigate to="目标路径" replace /> 组件,replace 表示替换历史记录(用户点后退不会回到重定向前的旧路径)。
jsx
<Routes>
<Route path="/old" element={<Navigate to="/new" replace />} />
<Route path="/" element={<Navigate to="/home" replace />} />
<Route path="/home" element={<Home />} />
<Route path="/new" element={<NewPage />} />
</Routes>
5. 404 兜底 ------ 匹配所有未定义路径
场景:用户输入了不存在的 URL,展示友好的"页面不存在"提示。
实现方式 :使用 path="*" 放在所有具体路由的最后面(路由匹配从上到下)。
jsx
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="*" element={<NotFound />} /> {/* 放在最后 */}
</Routes>
// NotFound.jsx
function NotFound() {
return <h2>404 - 您访问的页面不存在</h2>;
}
6. 鉴权拦截(路由守卫)------ 未登录跳转登录页
场景:某些页面(如个人中心、订单页)必须登录才能访问。
核心逻辑:
- 封装一个
AuthRoute组件,检查登录状态(如 token)。 - 若未登录,用
<Navigate>跳转到/login,并通过state把当前访问路径传过去。 - 登录成功后,从
state取出原始路径,跳转回去,提升用户体验。
jsx
// 1. 封装鉴权组件 AuthRoute.jsx
import { Navigate, useLocation } from 'react-router-dom';
function AuthRoute({ children }) {
const isLoggedIn = !!localStorage.getItem('token');
const location = useLocation();
if (!isLoggedIn) {
return <Navigate to="/login" state={{ from: location }} replace />;
}
return children;
}
// 2. 路由配置
<Route
path="/profile"
element={
<AuthRoute>
<Profile />
</AuthRoute>
}
/>
// 3. 登录页逻辑 Login.jsx
import { useLocation, useNavigate } from 'react-router-dom';
function Login() {
const location = useLocation();
const navigate = useNavigate();
const from = location.state?.from?.pathname || '/';
const handleLogin = () => {
// 登录成功后跳回来源页
navigate(from, { replace: true });
};
return <button onClick={handleLogin}>登录</button>;
}
踩坑提醒 :
state传递时务必用useLocation()返回的对象,不要手动传window.location(它是 DOM 对象,不可序列化,会报DataCloneError)。
四、核心 API 总结
| 路由模式 | 核心 API / 组件 | 解决什么问题 |
|---|---|---|
| 懒加载 | lazy + Suspense |
首屏加载慢,代码体积大 |
| 动态路由 | :id + useParams() |
需要从 URL 读取参数(如详情页 ID) |
| 嵌套路由 | <Outlet /> |
页面公共布局复用,局部刷新 |
| 重定向 | <Navigate to="..." replace /> |
路径变更、默认入口跳转 |
| 404 兜底 | path="*" |
匹配未知路径,提升用户体验 |
| 鉴权拦截 | 自定义 AuthRoute + location.state |
未登录禁止访问特定页面,并支持登录后回跳 |
五、开发黄金原则
- 路由顺序很重要 :具体路由写在前面,
path="*"写在最后。 - 导航别用
<a>:内部跳转务必使用<Link>或useNavigate(),否则会整页刷新丢失 SPA 状态。 - 状态传递用对象 :
state只传可序列化的数据(字符串、数字、普通对象),千万别传 DOM 元素或函数。
六、Vue vs React 路由核心差异
1. Vue 的"强制约定"
- Vue 官方脚手架强制要求路由文件放在
src/router/index.js。 - 路由采用配置对象数组 形式(
{ path: '/', component: Home })。 - 在
main.js中通过app.use(router)全局注入,App.vue只放<router-view />占位符,完全不关心路由表具体内容。 - 特点:强制将路由逻辑从视图层剥离。
2. React 的"灵活自由"
- React 没有强制规定路由文件位置,你有两种主流选择:
- 方式 A(常见) :直接在
App.js里写<Routes>和<Route>,即"组件即路由"的思维。 - 方式 B(更接近 Vue) :在
src/router/index.js里导出一个路由配置数组(React Router v6.4+ 支持createBrowserRouter),然后在main.js或App.js中引入。
- 方式 A(常见) :直接在
核心差别:
- React 把路由写成 JSX 标签 (
<Route path="/" element={<Home />} />) - Vue 把路由写成 纯数据对象 (
{ path: '/', component: Home })
七、React 启动链关系
理解 React 应用的启动顺序,有助于你更好地组织代码:
css
index.html(静态壳) → 加载 main.js(脚本入口) → 渲染 App.js(根组件)
1. index.html ------ 浏览器入口
html
<!DOCTYPE html>
<html>
<body>
<div id="root"></div> <!-- React 挂载锚点 -->
<script type="module" src="/src/main.js"></script>
</body>
</html>
它只做两件事:提供 div#root 容器和加载 main.js。
2. main.js ------ 应用脚本入口
jsx
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import { BrowserRouter } from 'react-router-dom';
ReactDOM.createRoot(document.getElementById('root')).render(
<BrowserRouter>
<App />
</BrowserRouter>
);
职责:初始化渲染引擎,挂载根组件。
3. App.js ------ 根组件
jsx
import { Routes, Route } from 'react-router-dom';
import Home from './pages/Home';
function App() {
return (
<div>
<Routes>
<Route path="/" element={<Home />} />
</Routes>
</div>
);
}
export default App;
职责:组织 UI 结构,承载路由与布局,是组件树的顶层。
| 维度 | main.js | App.js |
|---|---|---|
| 角色 | 入口脚本(项目入口文件) | 根组件(Root Component) |
| 是否组件 | ❌ 不是组件,是执行代码 | ✅ 是组件(函数/类) |
| 核心职责 | 初始化渲染引擎,挂载根节点 | 组织 UI 结构,承载路由与布局 |
| 修改频率 | 极少改动(如添加全局 Provider) | 频繁改动(新增页面路由) |
结语
React Router 是 React 生态中最核心的路由库,掌握它几乎是每个 React 开发者的必修课。本文从路由选型、基础配置到高阶技巧(懒加载、动态路由、嵌套路由、鉴权拦截等)进行了系统梳理,并对比了 Vue 与 React 的路由设计哲学,希望能帮你建立起完整的知识体系。
最后送大家三句话:
- 路由顺序决定匹配优先级。
- 内部导航用
Link,别用a标签。 - 状态传递记得序列化。
如果觉得有用,欢迎点赞、收藏、转发,让更多小伙伴受益!我们下期再见 👋