在现代前端单页面应用(SPA)开发中,路由管理是至关重要的一环。React Router 作为 React 生态中最流行的路由库,提供了两种主要的路由模式:HashRouter 和 BrowserRouter。本文将深入探讨这两种模式的实现原理、使用场景和差异。
1. React Router 简介
React Router 是 React 官方推荐的路由库,它通过管理 URL 与组件之间的映射关系,实现了单页面应用的多视图切换功能。目前 React Router 已发展到 v6 版本,提供了更加简洁和强大的 API。
1.1 安装 React Router
bash
npm install react-router-dom
1.2 基本使用
jsx
import React from 'react';
import { BrowserRouter, Routes, Route, Link } from 'react-router-dom';
import Home from './components/Home';
import About from './components/About';
import Contact from './components/Contact';
function App() {
return (
<BrowserRouter>
<nav>
<Link to="/">首页</Link>
<Link to="/about">关于</Link>
<Link to="/contact">联系</Link>
</nav>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/contact" element={<Contact />} />
</Routes>
</BrowserRouter>
);
}
export default App;
2. HashRouter 模式
2.1 什么是 HashRouter
HashRouter 使用 URL 的 hash 部分(即 # 号后面的内容)来管理路由。这种模式兼容性最好,可以在所有浏览器中运行,并且不需要服务器端配置。
示例 URL:
bash
http://example.com/#/home
http://example.com/#/about
http://example.com/#/users/123
2.2 HashRouter 实现原理
2.2.1 核心机制
HashRouter 的核心原理是利用 window.location.hash 属性和 hashchange 事件:
javascript
class SimpleHashRouter {
constructor() {
this.routes = {};
this.currentUrl = '';
// 监听 hashchange 事件
window.addEventListener('hashchange', this.refresh.bind(this), false);
window.addEventListener('load', this.refresh.bind(this), false);
}
// 注册路由
route(path, callback) {
this.routes[path] = callback || function() {};
}
// 路由刷新
refresh() {
this.currentUrl = window.location.hash.slice(1) || '/';
if (this.routes[this.currentUrl]) {
this.routes[this.currentUrl]();
}
}
// 导航到新路由
navigate(path) {
window.location.hash = '#' + path;
}
}
// 使用示例
const router = new SimpleHashRouter();
router.route('/', () => {
document.getElementById('content').innerHTML = '<h1>首页</h1>';
});
router.route('/about', () => {
document.getElementById('content').innerHTML = '<h1>关于我们</h1>';
});
2.2.2 React Router 中的 HashRouter 实现
React Router 的 HashRouter 组件内部实现更加复杂,但基本原理相同:
jsx
import React, { useState, useEffect } from 'react';
import { Router } from 'react-router-dom';
function HashRouter({ children }) {
const [location, setLocation] = useState({
pathname: window.location.hash.slice(1) || '/',
search: '',
hash: '',
state: null
});
useEffect(() => {
const handleHashChange = () => {
setLocation(prev => ({
...prev,
pathname: window.location.hash.slice(1) || '/'
}));
};
window.addEventListener('hashchange', handleHashChange);
return () => {
window.removeEventListener('hashchange', handleHashChange);
};
}, []);
const history = {
push: (path) => {
window.location.hash = '#' + path;
},
replace: (path) => {
window.location.replace('#' + path);
},
go: (n) => {
window.history.go(n);
},
goBack: () => {
window.history.back();
},
goForward: () => {
window.history.forward();
},
location
};
return (
<Router history={history} location={location}>
{children}
</Router>
);
}
2.3 HashRouter 使用示例
jsx
import React from 'react';
import { HashRouter, Routes, Route, Link, useNavigate } from 'react-router-dom';
function Home() {
const navigate = useNavigate();
return (
<div>
<h1>首页</h1>
<button onClick={() => navigate('/about')}>跳转到关于页面</button>
</div>
);
}
function About() {
return <h1>关于我们</h1>;
}
function User({ id }) {
return <h1>用户详情 - ID: {id}</h1>;
}
function App() {
return (
<HashRouter>
<nav>
<Link to="/">首页</Link>
<Link to="/about">关于</Link>
<Link to="/user/123">用户123</Link>
</nav>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/user/:id" element={<User />} />
</Routes>
</HashRouter>
);
}
export default App;
2.4 HashRouter 流程图
3. BrowserRouter 模式
3.1 什么是 BrowserRouter
BrowserRouter 使用 HTML5 History API 来管理路由,创建的是真实的 URL 路径,不包含 # 符号。这种模式创建的 URL 更加美观,更符合用户习惯。
示例 URL:
arduino
http://example.com/home
http://example.com/about
http://example.com/users/123
3.2 BrowserRouter 实现原理
3.2.1 History API 核心方法
BrowserRouter 依赖于 HTML5 History API,主要方法包括:
history.pushState(): 添加新的历史记录history.replaceState(): 替换当前历史记录popstate事件: 当用户导航历史记录时触发
javascript
class SimpleBrowserRouter {
constructor() {
this.routes = {};
this.currentUrl = '';
// 监听 popstate 事件
window.addEventListener('popstate', this.refresh.bind(this), false);
window.addEventListener('load', this.refresh.bind(this), false);
}
// 注册路由
route(path, callback) {
this.routes[path] = callback || function() {};
}
// 路由刷新
refresh() {
this.currentUrl = window.location.pathname || '/';
if (this.routes[this.currentUrl]) {
this.routes[this.currentUrl]();
}
}
// 导航到新路由
push(path) {
window.history.pushState(null, null, path);
this.refresh();
}
replace(path) {
window.history.replaceState(null, null, path);
this.refresh();
}
}
// 使用示例
const router = new SimpleBrowserRouter();
router.route('/', () => {
document.getElementById('content').innerHTML = '<h1>首页</h1>';
});
router.route('/about', () => {
document.getElementById('content').innerHTML = '<h1>关于我们</h1>';
});
3.2.2 React Router 中的 BrowserRouter 实现
React Router 的 BrowserRouter 组件实现:
jsx
import React, { useState, useEffect } from 'react';
import { Router } from 'react-router-dom';
function BrowserRouter({ children }) {
const [location, setLocation] = useState({
pathname: window.location.pathname,
search: window.location.search,
hash: window.location.hash,
state: null
});
useEffect(() => {
const handlePopState = () => {
setLocation({
pathname: window.location.pathname,
search: window.location.search,
hash: window.location.hash,
state: window.history.state
});
};
window.addEventListener('popstate', handlePopState);
return () => {
window.removeEventListener('popstate', handlePopState);
};
}, []);
const history = {
push: (path, state) => {
window.history.pushState(state, '', path);
setLocation({
pathname: window.location.pathname,
search: window.location.search,
hash: window.location.hash,
state
});
},
replace: (path, state) => {
window.history.replaceState(state, '', path);
setLocation({
pathname: window.location.pathname,
search: window.location.search,
hash: window.location.hash,
state
});
},
go: (n) => {
window.history.go(n);
},
goBack: () => {
window.history.back();
},
goForward: () => {
window.history.forward();
},
location
};
return (
<Router history={history} location={location}>
{children}
</Router>
);
}
3.3 BrowserRouter 使用示例
jsx
import React from 'react';
import { BrowserRouter, Routes, Route, Link, useParams } from 'react-router-dom';
function Home() {
return <h1>首页</h1>;
}
function About() {
return <h1>关于我们</h1>;
}
function User() {
const { id } = useParams();
return <h1>用户详情 - ID: {id}</h1>;
}
function NotFound() {
return <h1>404 - 页面未找到</h1>;
}
function App() {
return (
<BrowserRouter>
<nav>
<Link to="/">首页</Link>
<Link to="/about">关于</Link>
<Link to="/user/123">用户123</Link>
</nav>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/user/:id" element={<User />} />
<Route path="*" element={<NotFound />} />
</Routes>
</BrowserRouter>
);
}
export default App;
3.4 BrowserRouter 流程图
4. 两种模式的对比
4.1 功能特性对比
| 特性 | HashRouter | BrowserRouter |
|---|---|---|
| URL 美观度 | 较差(包含 #) | 较好(纯路径) |
| 兼容性 | 所有浏览器 | IE10+ |
| 服务器配置 | 不需要 | 需要配置支持 |
| SEO 友好性 | 较差 | 较好 |
| 实现原理 | hashchange 事件 | History API |
4.2 服务器配置要求
HashRouter 服务器配置
HashRouter 不需要特殊服务器配置,因为 # 后面的内容不会发送到服务器。
BrowserRouter 服务器配置
BrowserRouter 需要服务器配置,确保所有路由都返回 index.html:
Express 服务器配置:
javascript
const express = require('express');
const path = require('path');
const app = express();
// 静态文件服务
app.use(express.static(path.join(__dirname, 'build')));
// 所有路由都返回 index.html
app.get('*', function(req, res) {
res.sendFile(path.join(__dirname, 'build', 'index.html'));
});
app.listen(9000);
Nginx 服务器配置:
nginx
server {
listen 80;
server_name example.com;
root /usr/share/nginx/html;
index index.html;
location / {
try_files $uri $uri/ /index.html;
}
}
4.3 选择建议
-
使用 HashRouter 的情况:
- 静态网站托管(如 GitHub Pages)
- 不支持 History API 的旧浏览器
- 没有服务器配置权限
- 快速原型开发
-
使用 BrowserRouter 的情况:
- 有自己的服务器并可以配置
- 需要 SEO 友好的 URL
- 现代浏览器环境
- 生产环境应用
5. 实际应用示例
5.1 动态路由应用
jsx
import React, { useState, useEffect } from 'react';
import { BrowserRouter, Routes, Route, Link, useParams } from 'react-router-dom';
// 模拟数据获取
const fetchUser = (id) => {
return new Promise((resolve) => {
setTimeout(() => {
resolve({
id,
name: `用户 ${id}`,
email: `user${id}@example.com`
});
}, 500);
});
};
function UserList() {
const users = [
{ id: 1, name: '张三' },
{ id: 2, name: '李四' },
{ id: 3, name: '王五' }
];
return (
<div>
<h1>用户列表</h1>
<ul>
{users.map(user => (
<li key={user.id}>
<Link to={`/user/${user.id}`}>{user.name}</Link>
</li>
))}
</ul>
</div>
);
}
function UserDetail() {
const { id } = useParams();
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
const loadUser = async () => {
setLoading(true);
const userData = await fetchUser(id);
setUser(userData);
setLoading(false);
};
loadUser();
}, [id]);
if (loading) return <div>加载中...</div>;
if (!user) return <div>用户不存在</div>;
return (
<div>
<h1>用户详情</h1>
<p><strong>ID:</strong> {user.id}</p>
<p><strong>姓名:</strong> {user.name}</p>
<p><strong>邮箱:</strong> {user.email}</p>
</div>
);
}
function App() {
return (
<BrowserRouter>
<nav style={{ padding: '10px', borderBottom: '1px solid #ccc' }}>
<Link to="/" style={{ marginRight: '10px' }}>首页</Link>
<Link to="/users">用户列表</Link>
</nav>
<div style={{ padding: '20px' }}>
<Routes>
<Route path="/" element={<h1>欢迎来到用户管理系统</h1>} />
<Route path="/users" element={<UserList />} />
<Route path="/user/:id" element={<UserDetail />} />
</Routes>
</div>
</BrowserRouter>
);
}
export default App;
5.2 路由守卫示例
jsx
import React, { useState, useEffect } from 'react';
import { BrowserRouter, Routes, Route, Navigate, useLocation } from 'react-router-dom';
// 模拟认证状态
const useAuth = () => {
const [isAuthenticated, setIsAuthenticated] = useState(false);
const login = () => {
setIsAuthenticated(true);
localStorage.setItem('isAuthenticated', 'true');
};
const logout = () => {
setIsAuthenticated(false);
localStorage.removeItem('isAuthenticated');
};
useEffect(() => {
const authStatus = localStorage.getItem('isAuthenticated');
if (authStatus === 'true') {
setIsAuthenticated(true);
}
}, []);
return { isAuthenticated, login, logout };
};
// 路由守卫组件
function ProtectedRoute({ children }) {
const { isAuthenticated } = useAuth();
const location = useLocation();
if (!isAuthenticated) {
// 重定向到登录页,并保存当前路径以便登录后返回
return <Navigate to="/login" state={{ from: location }} replace />;
}
return children;
}
function LoginPage() {
const { login } = useAuth();
const location = useLocation();
const from = location.state?.from?.pathname || '/';
const handleLogin = () => {
login();
// 登录后跳转到之前尝试访问的页面或首页
window.location.href = from;
};
return (
<div>
<h1>登录页面</h1>
<button onClick={handleLogin}>登录</button>
</div>
);
}
function Dashboard() {
const { logout } = useAuth();
return (
<div>
<h1>仪表板</h1>
<p>这是受保护的页面</p>
<button onClick={logout}>退出登录</button>
</div>
);
}
function PublicPage() {
return <h1>公开页面</h1>;
}
function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<PublicPage />} />
<Route path="/login" element={<LoginPage />} />
<Route
path="/dashboard"
element={
<ProtectedRoute>
<Dashboard />
</ProtectedRoute>
}
/>
</Routes>
</BrowserRouter>
);
}
export default App;
6. 总结
React Router 提供了两种主要的路由模式:HashRouter 和 BrowserRouter,它们各有优缺点和适用场景。
- HashRouter 基于 URL hash 和 hashchange 事件,兼容性好,无需服务器配置,适合静态托管和简单应用。
- BrowserRouter 基于 HTML5 History API,URL 美观,SEO 友好,但需要服务器配置,适合现代浏览器和生产环境应用。
在实际开发中,应根据项目需求、目标用户浏览器环境和服务器配置能力来选择合适的路由模式。对于大多数现代 Web 应用,BrowserRouter 是更好的选择,因为它提供了更好的用户体验和开发体验。
无论选择哪种模式,React Router 都提供了强大而灵活的路由管理能力,可以帮助开发者构建复杂的单页面应用程序。