React Router DOM中的几种路由详解
1. React Router架构与核心概念
React Router是React生态系统中最核心的路由解决方案之一,为单页应用(SPA)提供声明式的路由管理。React Router DOM是专门为Web应用设计的路由库,基于History API实现客户端路由。
1.1 核心架构设计
graph TD
A[React Router Core] --> B[History Management]
A --> C[Route Matching]
A --> D[Navigation Control]
B --> E[BrowserRouter]
B --> F[HashRouter]
B --> G[MemoryRouter]
C --> H[Route Component]
C --> I[Routes Container]
C --> J[Outlet Rendering]
D --> K[Link Component]
D --> L[NavLink Component]
D --> M[useNavigate Hook]
1.2 核心概念解析
概念 | 作用 | 主要组件/Hook |
---|---|---|
Router Provider | 路由上下文提供者 | BrowserRouter, HashRouter |
Route Matching | 路由匹配与渲染 | Route, Routes |
Navigation | 路由导航控制 | Link, NavLink, useNavigate |
Route State | 路由状态管理 | useLocation, useParams |
1.3 版本演进与API变化
React Router经历了重大版本升级,v6版本引入了许多重要改进:
javascript
// React Router v5 (Legacy)
import { Switch, Route, useHistory } from 'react-router-dom';
function AppV5() {
const history = useHistory();
return (
<Switch>
<Route exact path="/" component={Home} />
<Route path="/about" component={About} />
</Switch>
);
}
// React Router v6 (Modern)
import { Routes, Route, useNavigate } from 'react-router-dom';
function AppV6() {
const navigate = useNavigate();
return (
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
</Routes>
);
}
2. 路由器类型详解
2.1 BrowserRouter - HTML5 History API路由器
BrowserRouter使用HTML5的History API来管理路由,提供干净的URL结构。
javascript
import { BrowserRouter, Routes, Route } from 'react-router-dom';
import { createRoot } from 'react-dom/client';
function App() {
return (
<BrowserRouter>
<div className="app">
<Navigation />
<Routes>
<Route path="/" element={<HomePage />} />
<Route path="/products" element={<ProductsPage />} />
<Route path="/products/:id" element={<ProductDetail />} />
<Route path="/about" element={<AboutPage />} />
<Route path="*" element={<NotFoundPage />} />
</Routes>
</div>
</BrowserRouter>
);
}
// 高级配置示例
function AppWithConfig() {
return (
<BrowserRouter
basename="/admin" // 应用基础路径
future={{
v7_startTransition: true, // 启用React 18并发特性
v7_relativeSplatPath: true // 启用相对路径特性
}}
>
<Routes>
{/* 路由配置 */}
</Routes>
</BrowserRouter>
);
}
const root = createRoot(document.getElementById('root'));
root.render(<App />);
BrowserRouter特点:
- URL结构:
https://example.com/products/123
- SEO友好,URL结构清晰
- 需要服务器配置支持,处理直接访问和刷新
- 支持浏览器前进后退按钮
2.2 HashRouter - Hash模式路由器
HashRouter使用URL的hash部分来管理路由,无需服务器端配置。
javascript
import { HashRouter, Routes, Route } from 'react-router-dom';
function HashApp() {
return (
<HashRouter>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/settings" element={<Settings />} />
</Routes>
</HashRouter>
);
}
// 适用场景:静态文件部署
function StaticDeploymentApp() {
return (
<HashRouter
hashType="slash" // URL格式: #/path
// hashType="noslash" // URL格式: #path
>
<Routes>
<Route path="/" element={<StaticHome />} />
<Route path="/docs" element={<Documentation />} />
</Routes>
</HashRouter>
);
}
HashRouter特点:
- URL结构:
https://example.com/#/products/123
- 无需服务器配置,适合静态部署
- SEO支持有限
- 兼容性更好,支持老旧浏览器
2.3 MemoryRouter - 内存路由器
MemoryRouter将路由历史记录保存在内存中,适用于测试环境和非浏览器环境。
javascript
import { MemoryRouter, Routes, Route } from 'react-router-dom';
// 测试环境使用
function TestApp({ initialEntries = ['/'] }) {
return (
<MemoryRouter
initialEntries={initialEntries}
initialIndex={0}
>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/login" element={<Login />} />
<Route path="/dashboard" element={<Dashboard />} />
</Routes>
</MemoryRouter>
);
}
// React Native应用中使用
function MobileApp() {
return (
<MemoryRouter>
<Routes>
<Route path="/" element={<MobileHome />} />
<Route path="/profile" element={<Profile />} />
</Routes>
</MemoryRouter>
);
}
2.4 路由器选择决策流程
flowchart TD
A[选择路由器类型] --> B{部署环境?}
B -->|Web应用| C{服务器配置?}
B -->|移动端/测试| H[MemoryRouter]
C -->|支持History API| D[BrowserRouter]
C -->|静态文件部署| E[HashRouter]
D --> F{SEO需求?}
E --> G{URL美观度?}
F -->|重要| I[推荐BrowserRouter + 服务器配置]
F -->|一般| J[BrowserRouter可选]
G -->|重要| K[考虑BrowserRouter]
G -->|一般| L[HashRouter适合]
3. Route组件深度解析
3.1 基础路由配置
javascript
import { Routes, Route } from 'react-router-dom';
function BasicRouting() {
return (
<Routes>
{/* 精确匹配根路径 */}
<Route path="/" element={<HomePage />} />
{/* 动态参数路由 */}
<Route path="/user/:id" element={<UserProfile />} />
{/* 可选参数路由 */}
<Route path="/posts/:id?" element={<PostList />} />
{/* 通配符路由 */}
<Route path="/files/*" element={<FileExplorer />} />
{/* 404路由 - 必须放在最后 */}
<Route path="*" element={<NotFound />} />
</Routes>
);
}
3.2 嵌套路由架构
嵌套路由是React Router的强大特性,允许构建复杂的应用结构。
javascript
import { Routes, Route, Outlet, useParams } from 'react-router-dom';
// 主路由配置
function App() {
return (
<Routes>
<Route path="/" element={<Layout />}>
<Route index element={<Home />} />
<Route path="about" element={<About />} />
{/* 嵌套路由:用户管理 */}
<Route path="users" element={<UsersLayout />}>
<Route index element={<UsersList />} />
<Route path=":id" element={<UserDetail />} />
<Route path=":id/edit" element={<UserEdit />} />
<Route path="new" element={<UserCreate />} />
</Route>
{/* 嵌套路由:产品管理 */}
<Route path="products/*" element={<ProductsApp />} />
</Route>
</Routes>
);
}
// 布局组件
function Layout() {
return (
<div className="app-layout">
<Header />
<nav>
<Link to="/">首页</Link>
<Link to="/about">关于</Link>
<Link to="/users">用户</Link>
<Link to="/products">产品</Link>
</nav>
<main>
<Outlet /> {/* 嵌套路由渲染位置 */}
</main>
<Footer />
</div>
);
}
// 用户模块布局
function UsersLayout() {
return (
<div className="users-layout">
<aside>
<h2>用户管理</h2>
<nav>
<Link to="/users">用户列表</Link>
<Link to="/users/new">新建用户</Link>
</nav>
</aside>
<section>
<Outlet /> {/* 用户子路由渲染 */}
</section>
</div>
);
}
// 产品模块 - 独立路由配置
function ProductsApp() {
return (
<Routes>
<Route index element={<ProductsList />} />
<Route path="category/:category" element={<CategoryProducts />} />
<Route path=":id" element={<ProductDetail />} />
<Route path=":id/reviews" element={<ProductReviews />} />
</Routes>
);
}
3.3 动态路由与参数提取
javascript
import { useParams, useSearchParams, useLocation } from 'react-router-dom';
// 路径参数提取
function UserProfile() {
const { id, tab } = useParams(); // 从 /user/:id/:tab 提取
return (
<div>
<h1>用户 #{id}</h1>
<UserTabs activeTab={tab} />
</div>
);
}
// 查询参数处理
function ProductsList() {
const [searchParams, setSearchParams] = useSearchParams();
const category = searchParams.get('category');
const sort = searchParams.get('sort') || 'name';
const page = parseInt(searchParams.get('page') || '1');
const updateFilter = (key, value) => {
setSearchParams(prev => {
const newParams = new URLSearchParams(prev);
if (value) {
newParams.set(key, value);
} else {
newParams.delete(key);
}
return newParams;
});
};
return (
<div>
<ProductFilter
category={category}
sort={sort}
onFilterChange={updateFilter}
/>
<ProductGrid
products={getProducts({ category, sort, page })}
/>
<Pagination
current={page}
total={getTotalPages()}
onChange={(page) => updateFilter('page', page)}
/>
</div>
);
}
// 位置信息获取
function LocationInfo() {
const location = useLocation();
return (
<div>
<p>当前路径: {location.pathname}</p>
<p>查询参数: {location.search}</p>
<p>Hash: {location.hash}</p>
<p>State: {JSON.stringify(location.state)}</p>
</div>
);
}
3.4 条件路由与权限控制
javascript
import { Navigate, useLocation } from 'react-router-dom';
// 权限路由组件
function ProtectedRoute({ children, requiredRole }) {
const { user, isAuthenticated } = useAuth();
const location = useLocation();
if (!isAuthenticated) {
// 重定向到登录页,并保存当前位置
return <Navigate to="/login" state={{ from: location }} replace />;
}
if (requiredRole && !user.roles.includes(requiredRole)) {
return <Navigate to="/unauthorized" replace />;
}
return children;
}
// 使用权限路由
function App() {
return (
<Routes>
<Route path="/login" element={<Login />} />
<Route path="/unauthorized" element={<Unauthorized />} />
{/* 需要登录的路由 */}
<Route
path="/dashboard"
element={
<ProtectedRoute>
<Dashboard />
</ProtectedRoute>
}
/>
{/* 需要管理员权限的路由 */}
<Route
path="/admin/*"
element={
<ProtectedRoute requiredRole="admin">
<AdminPanel />
</ProtectedRoute>
}
/>
{/* 条件重定向 */}
<Route
path="/profile"
element={
user ? <UserProfile /> : <Navigate to="/login" />
}
/>
</Routes>
);
}
4. 路由导航与状态管理
4.1 导航组件详解
javascript
import { Link, NavLink, useNavigate } from 'react-router-dom';
// 基础导航链接
function BasicNavigation() {
return (
<nav>
{/* 基础链接 */}
<Link to="/">首页</Link>
<Link to="/about">关于我们</Link>
{/* 相对路径链接 */}
<Link to="../parent">上级目录</Link>
<Link to="./child">子目录</Link>
{/* 带查询参数的链接 */}
<Link to="/search?q=react&type=tutorial">搜索</Link>
{/* 带状态的链接 */}
<Link
to="/detail"
state={{ from: 'navigation', data: { id: 1 } }}
>
详情页
</Link>
</nav>
);
}
// 活跃状态导航
function ActiveNavigation() {
return (
<nav className="main-nav">
<NavLink
to="/"
className={({ isActive, isPending }) =>
isActive ? 'nav-link active' : 'nav-link'
}
style={({ isActive }) => ({
color: isActive ? '#ff0000' : '#000000'
})}
>
首页
</NavLink>
<NavLink
to="/products"
className="nav-link"
end // 精确匹配,不匹配子路由
>
产品
</NavLink>
{/* 自定义活跃判断 */}
<NavLink
to="/blog"
className={({ isActive }) => {
// 自定义逻辑判断是否活跃
return `nav-link ${isActive ? 'active' : ''}`;
}}
>
博客
</NavLink>
</nav>
);
}
4.2 程序化导航
javascript
import { useNavigate, useLocation } from 'react-router-dom';
function ProgrammaticNavigation() {
const navigate = useNavigate();
const location = useLocation();
// 基础导航
const goToHome = () => {
navigate('/');
};
// 带参数导航
const goToUser = (userId) => {
navigate(`/user/${userId}`);
};
// 查询参数导航
const searchProducts = (query) => {
navigate(`/products?search=${encodeURIComponent(query)}`);
};
// 替换当前历史记录
const replaceToLogin = () => {
navigate('/login', { replace: true });
};
// 带状态导航
const goToDetailWithState = (product) => {
navigate('/product-detail', {
state: {
product,
returnTo: location.pathname
}
});
};
// 历史记录导航
const goBack = () => {
navigate(-1); // 后退一步
};
const goForward = () => {
navigate(1); // 前进一步
};
// 相对导航
const goToRelative = () => {
navigate('../sibling'); // 相对于当前路径
navigate('.', { replace: true }); // 刷新当前页面
};
return (
<div>
<button onClick={goToHome}>首页</button>
<button onClick={() => goToUser(123)}>用户详情</button>
<button onClick={() => searchProducts('laptop')}>搜索产品</button>
<button onClick={goBack}>返回</button>
<button onClick={goForward}>前进</button>
<button onClick={replaceToLogin}>去登录</button>
</div>
);
}
4.3 路由导航流程图
sequenceDiagram
participant User as 用户操作
participant Router as React Router
participant History as History API
participant Component as 组件系统
User->>Router: 点击Link/调用navigate
Router->>History: 更新浏览器历史
History->>Router: 触发location变化
Router->>Router: 执行路由匹配
alt 路由匹配成功
Router->>Component: 渲染匹配的组件
Component->>User: 显示新页面内容
else 路由不匹配
Router->>Component: 渲染404或重定向
Component->>User: 显示错误页面
end
Note over Router: 路由守卫检查
Note over Component: 组件生命周期执行
5. 高级路由特性
5.1 路由懒加载与代码分割
javascript
import { lazy, Suspense } from 'react';
import { Routes, Route } from 'react-router-dom';
// 懒加载组件定义
const HomePage = lazy(() => import('./pages/HomePage'));
const AboutPage = lazy(() => import('./pages/AboutPage'));
const ProductsPage = lazy(() => import('./pages/ProductsPage'));
const UserDashboard = lazy(() =>
import('./pages/UserDashboard').then(module => ({
default: module.UserDashboard
}))
);
// 自定义加载组件
function RouteLoadingSpinner() {
return (
<div className="route-loading">
<div className="spinner"></div>
<p>页面加载中...</p>
</div>
);
}
// 错误边界组件
class RouteErrorBoundary extends Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
return { hasError: true };
}
componentDidCatch(error, errorInfo) {
console.error('路由加载错误:', error, errorInfo);
}
render() {
if (this.state.hasError) {
return (
<div className="route-error">
<h2>页面加载失败</h2>
<button onClick={() => window.location.reload()}>
重新加载
</button>
</div>
);
}
return this.props.children;
}
}
// 路由配置
function App() {
return (
<Routes>
<Route
path="/"
element={
<RouteErrorBoundary>
<Suspense fallback={<RouteLoadingSpinner />}>
<HomePage />
</Suspense>
</RouteErrorBoundary>
}
/>
<Route
path="/about"
element={
<Suspense fallback={<RouteLoadingSpinner />}>
<AboutPage />
</Suspense>
}
/>
{/* 产品模块 - 整个模块懒加载 */}
<Route
path="/products/*"
element={
<Suspense fallback={<RouteLoadingSpinner />}>
<ProductsPage />
</Suspense>
}
/>
</Routes>
);
}
5.2 路由守卫实现
javascript
import { useEffect, useState } from 'react';
import { Navigate, useLocation } from 'react-router-dom';
// 路由守卫Hook
function useRouteGuard(guardConfig) {
const [guardResult, setGuardResult] = useState(null);
const location = useLocation();
useEffect(() => {
const executeGuards = async () => {
try {
// 执行前置守卫
if (guardConfig.beforeEnter) {
const result = await guardConfig.beforeEnter(location);
if (result === false) {
setGuardResult({ type: 'block' });
return;
}
if (typeof result === 'string') {
setGuardResult({ type: 'redirect', to: result });
return;
}
}
// 权限检查
if (guardConfig.requireAuth && !guardConfig.isAuthenticated()) {
setGuardResult({
type: 'redirect',
to: '/login',
state: { from: location }
});
return;
}
// 角色检查
if (guardConfig.requiredRoles) {
const userRoles = guardConfig.getUserRoles();
const hasRole = guardConfig.requiredRoles.some(role =>
userRoles.includes(role)
);
if (!hasRole) {
setGuardResult({ type: 'redirect', to: '/unauthorized' });
return;
}
}
setGuardResult({ type: 'allow' });
} catch (error) {
console.error('路由守卫执行错误:', error);
setGuardResult({ type: 'redirect', to: '/error' });
}
};
executeGuards();
}, [location, guardConfig]);
return guardResult;
}
// 守卫组件
function GuardedRoute({ children, ...guardConfig }) {
const guardResult = useRouteGuard(guardConfig);
if (!guardResult) {
return <div>检查权限中...</div>;
}
switch (guardResult.type) {
case 'allow':
return children;
case 'redirect':
return <Navigate
to={guardResult.to}
state={guardResult.state}
replace
/>;
case 'block':
return <div>访问被阻止</div>;
default:
return <div>未知守卫状态</div>;
}
}
// 使用守卫的路由配置
function App() {
const authService = useAuthService();
return (
<Routes>
{/* 公开路由 */}
<Route path="/login" element={<Login />} />
<Route path="/register" element={<Register />} />
{/* 需要登录的路由 */}
<Route
path="/dashboard"
element={
<GuardedRoute
requireAuth={true}
isAuthenticated={() => authService.isLoggedIn()}
beforeEnter={async (location) => {
// 异步权限检查
const hasAccess = await authService.checkAccess('/dashboard');
return hasAccess;
}}
>
<Dashboard />
</GuardedRoute>
}
/>
{/* 管理员路由 */}
<Route
path="/admin/*"
element={
<GuardedRoute
requireAuth={true}
requiredRoles={['admin', 'super-admin']}
isAuthenticated={() => authService.isLoggedIn()}
getUserRoles={() => authService.getUserRoles()}
>
<AdminPanel />
</GuardedRoute>
}
/>
</Routes>
);
}
5.3 路由数据预加载
javascript
import { useEffect, useState } from 'react';
import { useParams, useLoaderData } from 'react-router-dom';
// 路由数据加载器
export const productLoader = async ({ params }) => {
const { id } = params;
try {
const [product, reviews, recommendations] = await Promise.all([
fetchProduct(id),
fetchProductReviews(id),
fetchRecommendations(id)
]);
return {
product,
reviews,
recommendations
};
} catch (error) {
throw new Response('产品不存在', { status: 404 });
}
};
// 使用预加载数据的组件
function ProductDetail() {
const { product, reviews, recommendations } = useLoaderData();
const [loading, setLoading] = useState(false);
return (
<div className="product-detail">
<ProductInfo product={product} />
<ProductReviews reviews={reviews} />
<Recommendations items={recommendations} />
</div>
);
}
// 自定义数据预加载Hook
function useRouteData(dataLoader, deps = []) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
let cancelled = false;
const loadData = async () => {
try {
setLoading(true);
setError(null);
const result = await dataLoader();
if (!cancelled) {
setData(result);
}
} catch (err) {
if (!cancelled) {
setError(err);
}
} finally {
if (!cancelled) {
setLoading(false);
}
}
};
loadData();
return () => {
cancelled = true;
};
}, deps);
return { data, loading, error };
}
// 使用自定义预加载Hook
function UserProfile() {
const { id } = useParams();
const { data: user, loading, error } = useRouteData(
() => fetchUserProfile(id),
[id]
);
if (loading) return <UserProfileSkeleton />;
if (error) return <ErrorMessage error={error} />;
if (!user) return <UserNotFound />;
return <UserProfileContent user={user} />;
}
6. 性能优化与最佳实践
6.1 路由性能优化策略
javascript
import { memo, useMemo, useCallback } from 'react';
import { useLocation, matchPath } from 'react-router-dom';
// 路由缓存优化
const RouteCache = new Map();
function useCachedRoute(routeKey, routeComponent) {
return useMemo(() => {
if (!RouteCache.has(routeKey)) {
RouteCache.set(routeKey, routeComponent);
}
return RouteCache.get(routeKey);
}, [routeKey]);
}
// 智能路由匹配
function useSmartRouteMatch(patterns) {
const location = useLocation();
return useMemo(() => {
for (const pattern of patterns) {
const match = matchPath(pattern, location.pathname);
if (match) {
return { pattern, match };
}
}
return null;
}, [location.pathname, patterns]);
}
// 性能优化的导航组件
const OptimizedNavigation = memo(function Navigation({ items }) {
const location = useLocation();
const navItems = useMemo(() => {
return items.map(item => ({
...item,
isActive: location.pathname === item.path
}));
}, [items, location.pathname]);
const handleNavClick = useCallback((path) => {
// 预加载目标页面资源
if (path !== location.pathname) {
preloadRoute(path);
}
}, [location.pathname]);
return (
<nav>
{navItems.map(item => (
<NavLink
key={item.path}
to={item.path}
className={item.isActive ? 'active' : ''}
onClick={() => handleNavClick(item.path)}
>
{item.label}
</NavLink>
))}
</nav>
);
});
// 路由预加载
function preloadRoute(path) {
const routeMap = {
'/products': () => import('./pages/ProductsPage'),
'/about': () => import('./pages/AboutPage'),
'/contact': () => import('./pages/ContactPage')
};
const preloader = routeMap[path];
if (preloader) {
// 在空闲时预加载
if ('requestIdleCallback' in window) {
requestIdleCallback(() => preloader());
} else {
setTimeout(preloader, 100);
}
}
}
6.2 路由状态管理
javascript
import { createContext, useContext, useReducer } from 'react';
import { useLocation, useNavigate } from 'react-router-dom';
// 路由状态上下文
const RouteStateContext = createContext();
// 路由状态管理器
function routeStateReducer(state, action) {
switch (action.type) {
case 'SET_BREADCRUMB':
return {
...state,
breadcrumb: action.payload
};
case 'SET_PAGE_TITLE':
return {
...state,
pageTitle: action.payload
};
case 'SET_NAVIGATION_STATE':
return {
...state,
navigation: {
...state.navigation,
...action.payload
}
};
case 'ADD_ROUTE_HISTORY':
return {
...state,
routeHistory: [
...state.routeHistory.slice(-9), // 保留最近10条记录
action.payload
]
};
default:
return state;
}
}
// 路由状态提供者
function RouteStateProvider({ children }) {
const [state, dispatch] = useReducer(routeStateReducer, {
breadcrumb: [],
pageTitle: '',
navigation: {
isCollapsed: false,
activeMenu: null
},
routeHistory: []
});
const location = useLocation();
useEffect(() => {
// 记录路由历史
dispatch({
type: 'ADD_ROUTE_HISTORY',
payload: {
path: location.pathname,
search: location.search,
timestamp: Date.now()
}
});
}, [location]);
return (
<RouteStateContext.Provider value={{ state, dispatch }}>
{children}
</RouteStateContext.Provider>
);
}
// 路由状态Hook
function useRouteState() {
const context = useContext(RouteStateContext);
if (!context) {
throw new Error('useRouteState must be used within RouteStateProvider');
}
return context;
}
// 面包屑导航Hook
function useBreadcrumb() {
const { state, dispatch } = useRouteState();
const location = useLocation();
const setBreadcrumb = useCallback((breadcrumb) => {
dispatch({ type: 'SET_BREADCRUMB', payload: breadcrumb });
}, [dispatch]);
// 自动生成面包屑
const generateBreadcrumb = useCallback(() => {
const pathSegments = location.pathname.split('/').filter(Boolean);
const breadcrumb = pathSegments.map((segment, index) => {
const path = '/' + pathSegments.slice(0, index + 1).join('/');
return {
label: segment.charAt(0).toUpperCase() + segment.slice(1),
path: path,
isActive: index === pathSegments.length - 1
};
});
setBreadcrumb([{ label: '首页', path: '/', isActive: false }, ...breadcrumb]);
}, [location.pathname, setBreadcrumb]);
return {
breadcrumb: state.breadcrumb,
setBreadcrumb,
generateBreadcrumb
};
}
6.3 路由监控与分析
javascript
import { useEffect } from 'react';
import { useLocation } from 'react-router-dom';
// 路由性能监控Hook
function useRouteAnalytics() {
const location = useLocation();
useEffect(() => {
const startTime = performance.now();
// 页面视图统计
if (window.gtag) {
window.gtag('config', 'GA_MEASUREMENT_ID', {
page_path: location.pathname + location.search
});
}
// 自定义分析
const routeData = {
path: location.pathname,
search: location.search,
hash: location.hash,
timestamp: Date.now(),
userAgent: navigator.userAgent,
referrer: document.referrer
};
// 发送路由访问数据
fetch('/api/analytics/route', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(routeData)
}).catch(error => {
console.warn('路由分析数据发送失败:', error);
});
// 性能监控
const observer = new PerformanceObserver((list) => {
const entries = list.getEntries();
entries.forEach(entry => {
if (entry.entryType === 'navigation') {
// 记录页面加载性能
const performanceData = {
path: location.pathname,
loadTime: entry.loadEventEnd - entry.loadEventStart,
domContentLoaded: entry.domContentLoadedEventEnd - entry.domContentLoadedEventStart,
firstPaint: entry.responseEnd - entry.requestStart
};
fetch('/api/analytics/performance', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(performanceData)
});
}
});
});
observer.observe({ entryTypes: ['navigation'] });
return () => {
observer.disconnect();
// 记录页面停留时间
const endTime = performance.now();
const stayTime = endTime - startTime;
if (stayTime > 1000) { // 停留超过1秒才记录
fetch('/api/analytics/stay-time', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
path: location.pathname,
stayTime: Math.round(stayTime)
})
});
}
};
}, [location]);
}
// 在应用中使用监控
function App() {
useRouteAnalytics();
return (
<RouteStateProvider>
<BrowserRouter>
<Routes>
{/* 路由配置 */}
</Routes>
</BrowserRouter>
</RouteStateProvider>
);
}
7. 测试与调试
7.1 路由组件测试
javascript
import { render, screen, fireEvent } from '@testing-library/react';
import { MemoryRouter } from 'react-router-dom';
import { vi } from 'vitest';
// 路由组件测试工具
function renderWithRouter(component, { initialEntries = ['/'] } = {}) {
return render(
<MemoryRouter initialEntries={initialEntries}>
{component}
</MemoryRouter>
);
}
// 路由导航测试
describe('Navigation Component', () => {
test('应该正确渲染导航链接', () => {
renderWithRouter(<Navigation />);
expect(screen.getByText('首页')).toBeInTheDocument();
expect(screen.getByText('产品')).toBeInTheDocument();
expect(screen.getByText('关于')).toBeInTheDocument();
});
test('应该正确处理链接点击', () => {
const { container } = renderWithRouter(<App />, {
initialEntries: ['/']
});
const productLink = screen.getByText('产品');
fireEvent.click(productLink);
expect(screen.getByText('产品列表页面')).toBeInTheDocument();
});
test('应该正确处理404页面', () => {
renderWithRouter(<App />, {
initialEntries: ['/non-existent-page']
});
expect(screen.getByText('页面未找到')).toBeInTheDocument();
});
});
// 路由守卫测试
describe('Protected Routes', () => {
test('未登录用户应该被重定向到登录页', () => {
const mockAuthService = {
isLoggedIn: () => false,
getUserRoles: () => []
};
renderWithRouter(
<AuthProvider value={mockAuthService}>
<App />
</AuthProvider>,
{ initialEntries: ['/dashboard'] }
);
expect(screen.getByText('登录页面')).toBeInTheDocument();
});
test('已登录用户应该能访问受保护页面', () => {
const mockAuthService = {
isLoggedIn: () => true,
getUserRoles: () => ['user']
};
renderWithRouter(
<AuthProvider value={mockAuthService}>
<App />
</AuthProvider>,
{ initialEntries: ['/dashboard'] }
);
expect(screen.getByText('用户仪表板')).toBeInTheDocument();
});
});
7.2 路由调试工具
javascript
import { useEffect } from 'react';
import { useLocation, useNavigationType } from 'react-router-dom';
// 路由调试Hook
function useRouteDebugger(enabled = process.env.NODE_ENV === 'development') {
const location = useLocation();
const navigationType = useNavigationType();
useEffect(() => {
if (!enabled) return;
const routeInfo = {
pathname: location.pathname,
search: location.search,
hash: location.hash,
state: location.state,
navigationType: navigationType,
timestamp: new Date().toISOString()
};
console.group('🛣️ Route Change');
console.log('Location:', routeInfo);
console.log('Full URL:', window.location.href);
console.groupEnd();
// 在控制台暴露路由信息
window.__ROUTE_DEBUG__ = {
...routeInfo,
history: window.history,
back: () => window.history.back(),
forward: () => window.history.forward(),
go: (delta) => window.history.go(delta)
};
}, [location, navigationType, enabled]);
}
// 路由开发工具组件
function RouteDevTools() {
const location = useLocation();
const [isOpen, setIsOpen] = useState(false);
if (process.env.NODE_ENV !== 'development') {
return null;
}
return (
<div className="route-devtools">
<button
className="devtools-toggle"
onClick={() => setIsOpen(!isOpen)}
>
🛣️
</button>
{isOpen && (
<div className="devtools-panel">
<h3>路由信息</h3>
<div>
<strong>路径:</strong> {location.pathname}
</div>
<div>
<strong>查询:</strong> {location.search || '无'}
</div>
<div>
<strong>Hash:</strong> {location.hash || '无'}
</div>
<div>
<strong>状态:</strong>
<pre>{JSON.stringify(location.state, null, 2)}</pre>
</div>
<h4>快速导航</h4>
<div className="quick-nav">
<button onClick={() => window.history.back()}>
← 后退
</button>
<button onClick={() => window.history.forward()}>
前进 →
</button>
<button onClick={() => window.location.reload()}>
🔄 刷新
</button>
</div>
</div>
)}
</div>
);
}
8. 总结与最佳实践
8.1 核心价值总结
React Router DOM为React应用提供了强大而灵活的路由解决方案:
- 声明式路由配置: 通过JSX组件的方式配置路由,直观易懂
- 嵌套路由支持: 支持复杂的嵌套路由结构,适合大型应用
- 动态路由匹配: 灵活的参数提取和路径匹配机制
- 程序化导航: 提供丰富的API进行程序控制导航
8.2 路由选择决策指南
- BrowserRouter: 适用于需要SEO优化的现代Web应用
- HashRouter: 适用于静态部署或需要兼容老旧浏览器的场景
- MemoryRouter: 适用于测试环境或非浏览器环境
8.3 性能优化建议
- 懒加载: 使用React.lazy()和Suspense实现路由级别的代码分割
- 预加载: 在用户交互时预加载可能访问的路由组件
- 缓存策略: 合理使用useMemo和useCallback优化路由组件渲染
- 监控分析: 建立路由性能监控体系,及时发现和解决问题
8.4 开发最佳实践
- 统一路由管理: 建立统一的路由配置和管理机制
- 权限控制: 实现完善的路由守卫和权限检查
- 错误处理: 添加适当的错误边界和404页面处理
- 测试覆盖: 为路由功能编写充分的单元测试和集成测试
通过合理运用React Router DOM的各种特性,我们能够构建出结构清晰、性能优良、用户体验优秀的单页应用。在实际项目中,应根据具体需求选择合适的路由策略,并结合现代前端工程化实践,打造高质量的路由系统。