React Router 完全指南:从 HashRouter 到 BrowserRouter

一、前端路由的演进

1. 传统后端路由时代

在早期 Web 开发中,路由完全由后端控制:

bash 复制代码
// 传统的后端路由
app.get('/about', (req, res) => {
  res.render('about'); // 返回完整的 HTML 页面
});

app.get('/users/:id', (req, res) => {
  const user = getUser(req.params.id);
  res.render('user', { user });
});

缺点:

  • 每次切换页面都需要重新加载整个页面

  • 页面切换时会出现空白期

  • 用户体验差,交互不流畅

  • 服务器压力大

2. SPA 与前端路由的诞生

随着移动互联网的发展,单页应用(SPA)成为主流:

bash 复制代码
<!-- 传统页面跳转 -->
<a href="/about">关于我们</a>  <!-- 会刷新整个页面 -->

<!-- SPA 路由跳转 -->
<a href="#/about">关于我们</a>  <!-- 不会刷新页面 -->

Hash 路由的工作原理:

  • URL 的 hash 部分(# 后面的内容)改变不会触发页面刷新

  • 通过监听 hashchange 事件来响应路由变化

  • 原本用于页面内锚点跳转的特性被用于路由

bash 复制代码
// Hash 路由实现原理
window.addEventListener('hashchange', function() {
  const hash = window.location.hash.slice(1);
  renderPage(hash);
});

function renderPage(route) {
  // 根据路由渲染不同组件
  const app = document.getElementById('app');
  switch(route) {
    case 'home':
      app.innerHTML = '<h1>首页</h1>';
      break;
    case 'about':
      app.innerHTML = '<h1>关于我们</h1>';
      break;
    default:
      app.innerHTML = '<h1>404</h1>';
  }
}

二、React Router 基础

React Router 是 React 生态中最流行的路由解决方案。

1. 安装与配置

bash 复制代码
# 安装 React Router
npm install react-router-dom

2. 基本路由配置

bash 复制代码
import React from 'react';
import { BrowserRouter, Routes, Route, Link } from 'react-router-dom';
import Home from './pages/Home';
import About from './pages/About';
import Users from './pages/Users';

function App() {
  return (
    <BrowserRouter>
      <nav>
        <Link to="/">首页</Link>
        <Link to="/about">关于</Link>
        <Link to="/users">用户</Link>
      </nav>
      
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/about" element={<About />} />
        <Route path="/users" element={<Users />} />
        <Route path="*" element={<NotFound />} />
      </Routes>
    </BrowserRouter>
  );
}

3. HashRouter vs BrowserRouter

特性 HashRouter BrowserRouter
URL 格式 example.com/#/about example.com/about
美观度 较丑(包含 #) 美观
服务器配置 不需要 需要配置
SEO 不友好 友好
浏览器支持 所有浏览器 HTML5 浏览器

HashRouter 示例

bash 复制代码
import { HashRouter, Routes, Route } from 'react-router-dom';

function App() {
  return (
    <HashRouter>
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/about" element={<About />} />
      </Routes>
    </HashRouter>
  );
}
// 实际 URL: http://localhost:3000/#/about

BrowserRouter 示例

bash 复制代码
import { BrowserRouter, Routes, Route } from 'react-router-dom';

function App() {
  return (
    <BrowserRouter>
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/about" element={<About />} />
      </Routes>
    </BrowserRouter>
  );
}
// 实际 URL: http://localhost:3000/about

三、高级路由特性

1. 路由懒加载(性能优化)

懒加载可以显著提升首屏加载速度:

bash 复制代码
import React, { lazy, Suspense } from 'react';
import { BrowserRouter, Routes, Route } from 'react-router-dom';

// 使用 lazy 进行代码分割
const Home = lazy(() => import('./pages/Home'));
const About = lazy(() => import('./pages/About'));
const Dashboard = lazy(() => import('./pages/Dashboard'));

function App() {
  return (
    <BrowserRouter>
      <Suspense fallback={<div>加载中...</div>}>
        <Routes>
          <Route path="/" element={<Home />} />
          <Route path="/about" element={<About />} />
          <Route path="/dashboard/*" element={<Dashboard />} />
        </Routes>
      </Suspense>
    </BrowserRouter>
  );
}

性能对比:

  • 未使用懒加载:所有组件一次性加载,初始加载慢

  • 使用懒加载:只加载当前页面,切换时按需加载

2. 动态路由

处理 URL 参数:

bash 复制代码
import { useParams, useNavigate } from 'react-router-dom';

function UserProfile() {
  const { userId } = useParams(); // 获取动态参数
  const navigate = useNavigate();
  
  return (
    <div>
      <h1>用户 ID: {userId}</h1>
      <button onClick={() => navigate('/users')}>
        返回用户列表
      </button>
    </div>
  );
}

// 路由配置
<Route path="/users/:userId" element={<UserProfile />} />

3. 嵌套路由

构建复杂的页面布局:

bash 复制代码
import { Routes, Route, Outlet, Link } from 'react-router-dom';

// 布局组件
function DashboardLayout() {
  return (
    <div>
      <nav>
        <Link to="/dashboard/overview">概览</Link>
        <Link to="/dashboard/analytics">数据</Link>
        <Link to="/dashboard/settings">设置</Link>
      </nav>
      <main>
        <Outlet /> {/* 子路由渲染位置 */}
      </main>
    </div>
  );
}

// 子组件
function Overview() {
  return <h2>概览页面</h2>;
}

function Analytics() {
  return <h2>数据页面</h2>;
}

function Settings() {
  return <h2>设置页面</h2>;
}

// 路由配置
function App() {
  return (
    <Routes>
      <Route path="/dashboard" element={<DashboardLayout />}>
        <Route path="overview" element={<Overview />} />
        <Route path="analytics" element={<Analytics />} />
        <Route path="settings" element={<Settings />} />
      </Route>
    </Routes>
  );
}

4. 404 页面处理

bash 复制代码
import { Routes, Route } from 'react-router-dom';

function NotFound() {
  return (
    <div>
      <h1>404</h1>
      <p>页面未找到</p>
      <Link to="/">返回首页</Link>
    </div>
  );
}

// 路由配置
<Routes>
  <Route path="/" element={<Home />} />
  <Route path="/about" element={<About />} />
  <Route path="*" element={<NotFound />} /> {/* 404 路由 */}
</Routes>

四、路由鉴权(权限控制)

1. 基础鉴权实现

bash 复制代码
import { useState } from 'react';
import { Routes, Route, Navigate, useNavigate } from 'react-router-dom';

// 模拟登录状态
function useAuth() {
  const [user, setUser] = useState(null);
  
  const login = (username, password) => {
    // 模拟验证
    if (username === 'admin' && password === '123456') {
      setUser({ username: 'admin', role: 'admin' });
      localStorage.setItem('user', JSON.stringify({ username: 'admin' }));
      return true;
    }
    return false;
  };
  
  const logout = () => {
    setUser(null);
    localStorage.removeItem('user');
  };
  
  return { user, login, logout };
}

// 路由守卫组件
function PrivateRoute({ children }) {
  const { user } = useAuth();
  
  if (!user) {
    return <Navigate to="/login" replace />;
  }
  
  return children;
}

// 登录页面
function Login() {
  const [username, setUsername] = useState('');
  const [password, setPassword] = useState('');
  const { login } = useAuth();
  const navigate = useNavigate();
  
  const handleSubmit = (e) => {
    e.preventDefault();
    if (login(username, password)) {
      navigate('/dashboard');
    } else {
      alert('登录失败,请检查用户名和密码');
    }
  };
  
  return (
    <form onSubmit={handleSubmit}>
      <input
        type="text"
        placeholder="用户名"
        value={username}
        onChange={(e) => setUsername(e.target.value)}
      />
      <input
        type="password"
        placeholder="密码"
        value={password}
        onChange={(e) => setPassword(e.target.value)}
      />
      <button type="submit">登录</button>
    </form>
  );
}

// 主应用
function App() {
  return (
    <Routes>
      <Route path="/login" element={<Login />} />
      <Route path="/dashboard" element={
        <PrivateRoute>
          <Dashboard />
        </PrivateRoute>
      } />
      <Route path="/" element={<Home />} />
    </Routes>
  );
}

2. 基于角色的权限控制

bash 复制代码
function RoleBasedRoute({ children, requiredRole }) {
  const { user } = useAuth();
  
  if (!user) {
    return <Navigate to="/login" replace />;
  }
  
  if (user.role !== requiredRole) {
    return <Navigate to="/forbidden" replace />;
  }
  
  return children;
}

// 使用
<Route 
  path="/admin" 
  element={
    <RoleBasedRoute requiredRole="admin">
      <AdminPanel />
    </RoleBasedRoute>
  } 
/>

五、props.children 与布局组件

1. 基础概念

props.children 是 React 的特殊属性,代表组件标签之间的内容:

bash 复制代码
// 容器组件
function Container({ children }) {
  return (
    <div className="container">
      {children}
    </div>
  );
}

// 使用
<Container>
  <h1>标题</h1>
  <p>内容</p>
</Container>

2. 弹窗组件实现

bash 复制代码
import React, { useState } from 'react';
import { createPortal } from 'react-dom';

function Modal({ isOpen, onClose, title, children }) {
  if (!isOpen) return null;
  
  return createPortal(
    <div className="modal-overlay" onClick={onClose}>
      <div className="modal-content" onClick={(e) => e.stopPropagation()}>
        <div className="modal-header">
          <h2>{title}</h2>
          <button onClick={onClose}>&times;</button>
        </div>
        <div className="modal-body">
          {children}
        </div>
        <div className="modal-footer">
          <button onClick={onClose}>关闭</button>
        </div>
      </div>
    </div>,
    document.getElementById('modal-root')
  );
}

// 使用
function App() {
  const [showModal, setShowModal] = useState(false);
  
  return (
    <div>
      <button onClick={() => setShowModal(true)}>打开弹窗</button>
      
      <Modal 
        isOpen={showModal} 
        onClose={() => setShowModal(false)}
        title="提示"
      >
        <p>这是弹窗内容,可以被完全自定义</p>
        <input type="text" placeholder="输入内容" />
        <button>自定义按钮</button>
      </Modal>
    </div>
  );
}

六、路由最佳实践

1. 路由配置文件

bash 复制代码
// router.config.js
const routes = [
  {
    path: '/',
    element: <Layout />,
    children: [
      {
        path: '',
        element: <Home />,
      },
      {
        path: 'about',
        element: <About />,
      },
      {
        path: 'users',
        element: <Users />,
        children: [
          {
            path: ':id',
            element: <UserProfile />,
          },
        ],
      },
    ],
  },
  {
    path: 'login',
    element: <Login />,
  },
  {
    path: '*',
    element: <NotFound />,
  },
];

export default routes;

2. 路由守卫 HOC

bash 复制代码
// withAuth.js
import { Navigate } from 'react-router-dom';

function withAuth(Component) {
  return function AuthenticatedComponent(props) {
    const token = localStorage.getItem('token');
    
    if (!token) {
      return <Navigate to="/login" replace />;
    }
    
    return <Component {...props} />;
  };
}

// 使用
const DashboardWithAuth = withAuth(Dashboard);

总结

React Router 是现代 React 应用不可或缺的工具:

  1. HashRouter vs BrowserRouter

    • HashRouter:简单、无需服务器配置,但 URL 不美观

    • BrowserRouter:URL 美观、SEO 友好,需要服务器支持

  2. 核心特性

    • 路由懒加载提升性能

    • 动态路由处理参数

    • 嵌套路由构建复杂布局

    • 路由鉴权控制访问权限

  3. 最佳实践

    • 集中管理路由配置

    • 使用懒加载优化首屏

    • 实现完整的权限控制

    • 统一的错误页面处理

掌握这些概念和技巧,你就能构建出专业、高性能的单页应用了!

相关推荐
妙码生花35 分钟前
从 PHP 到 AI + Golang,程序员自救转型手记(五十六):附件管理、增加根据文件后缀生成 SVG 文件图标的接口
前端·后端·go
栀鸢ouo36 分钟前
useTableHeight:一个优雅的 Vue 3 表格自适应高度解决方案
前端
橘子星36 分钟前
一篇文章搞懂 useRef:聚焦 DOM、模拟 forceRender、管理 Worker 都靠它
前端·javascript
前端Hardy39 分钟前
Vue 终于杀进终端界!这个开源项目让 CLI 开发像写网页一样简单
前端·javascript·后端
一次旅行1 小时前
多智能体编排实战:拆解Plan-and-Execute范式+三层记忆架构,手写无依赖轻量Agent调度引擎
前端·javascript·架构
MindUp2 小时前
面试录音视频的AI复盘方案:从语音转录到RAG问答的技术实践
人工智能·面试·音视频
小玮看世界2 小时前
[Python]从“脏”数据到优雅实现:一个IoT滑动窗口最大值问题的测试驱动优化实录
linux·前端·python
尤乐娃子3 小时前
进入大厂(厂子大)实习Day11
前端·笔记·实习
李文旺3 小时前
意图识别精准度升级方案
面试