在现代前端开发中,单页应用(SPA) 已成为主流。这类应用通过 前端路由 实现页面切换,而无需每次都向后端请求完整的 HTML 页面。 前端路由的核心在于:根据 URL 的变化动态渲染不同的内容,但不刷新整个页面。
在实现方式上,最常用的两种模式是:
hash模式(基于window.location.hash和hashchange事件)history模式(基于 HTML5 提供的pushState()、replaceState()和popstate事件)
本文将从零开始讲解这两种模式的工作原理、具体用法,并提供完整的示例代码,帮助你真正掌握如何使用它们来构建一个简单的前端路由系统。
一、Hash 模式
1. 原理简介
URL 中 # 号后面的内容被称为 hash,例如:
https://www.aaa.com/#/home
浏览器对 hash 的处理有以下特性:
- 修改
location.hash不会触发页面刷新。 - 当 hash 发生变化时,会触发
hashchange事件。 - hash 不会被发送到服务器。
这些特性非常适合用于前端路由。
2. 核心方法和事件
| 方法/事件 | 描述 |
|---|---|
window.location.hash | 获取或设置当前 URL 的 hash 部分 |
window.addEventListener('hashchange', callback) | 监听 hash 的变化 |
3. 示例:手动实现一个 Hash 路由
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>Hash 路由示例</title>
</head>
<body>
<nav>
<a href="#/home">首页</a>
<a href="#/about">关于</a>
<a href="#/contact">联系</a>
</nav>
<div id="app"></div>
<script>
// 定义路由表
const routes = {
'/home': '<h2>这是首页</h2><p>欢迎来到主页。</p>',
'/about': '<h2>这是关于页面</h2><p>了解更多关于我们。</p>',
'/contact': '<h2>这是联系页面</h2><p>请联系我们获取更多信息。</p>'
};
// 渲染函数
function render() {
const path = window.location.hash.slice(1) || '/home'; // 去除 #
const content = routes[path] || '<h2>404 页面未找到</h2>';
document.getElementById('app').innerHTML = content;
}
// 初始加载
window.addEventListener('DOMContentLoaded', render);
// hash 变化时重新渲染
window.addEventListener('hashchange', render);
</script>
</body>
</html>
4. 优缺点
-
✅ 优点:
- 简单易用,兼容性好(支持 IE8+)。
- 不需要服务器配置。
- 不会触发页面刷新。
-
❌ 缺点:
- URL 中带有
#,不够美观。 - 对 SEO 不友好。
- 不符合传统 URL 风格。
- URL 中带有
二、History 模式
1. 原理简介
HTML5 提供了新的 API 来操作浏览器的历史记录栈,使得我们可以在不刷新页面的前提下改变 URL。
主要依赖的方法有:
history.pushState(state, title, url):向历史记录栈添加一条新记录。history.replaceState(state, title, url):替换当前历史记录。popstate事件:当用户点击“前进”或“后退”按钮时触发。
2. 核心方法和事件
| 方法/事件 | 描述 |
|---|---|
history.pushState(state, title, url) | 添加一条新的历史记录 |
history.replaceState(state, title, url) | 替换当前历史记录 |
window.addEventListener('popstate', callback) | 监听浏览器前进/后退动作 |
3. 示例:手动实现一个 History 路由
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>History 路由示例</title>
</head>
<body>
<button onclick="navigate('/home')">首页</button>
<button onclick="navigate('/about')">关于</button>
<button onclick="navigate('/contact')">联系</button>
<div id="app">当前视图</div>
<script>
// 路由映射表
const routes = {
'/home': '<h2>首页</h2><p>欢迎来到主页。</p>',
'/about': '<h2>关于</h2><p>了解更多关于我们。</p>',
'/contact': '<h2>联系</h2><p>请联系我们获取更多信息。</p>'
};
// 导航函数
function navigate(path) {
history.pushState({ path }, '', path); // 添加到历史栈
render(path);
}
// 替换当前路径(不会新增历史记录)
function replace(path) {
history.replaceState({ path }, '', path);
render(path);
}
// 渲染函数
function render(path) {
const content = routes[path] || '<h2>404 页面未找到</h2>';
document.getElementById('app').innerHTML = content;
}
// 初始加载
window.addEventListener('DOMContentLoaded', () => {
const path = location.pathname;
render(path);
});
// 监听 popstate 事件(前进/后退)
window.addEventListener('popstate', (event) => {
const path = event.state?.path || location.pathname;
render(path);
});
</script>
</body>
</html>
4. 服务器配置要求
使用 History 模式时,如果用户直接访问 /about 这样的路径,浏览器会向服务器请求该路径下的资源,而服务器如果没有正确配置,就会返回 404。
因此必须确保服务器将所有请求都重定向到你的入口文件(如 index.html)。
示例:Nginx 配置
location / {
try_files $uri $uri/ /index.html;
}
Apache 配置
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
RewriteRule ^index\.html$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.html [L]
</IfModule>
5. 优缺点
-
✅ 优点:
- URL 更加美观,没有
#。 - 更利于 SEO。
- 支持更复杂的路由逻辑。
- URL 更加美观,没有
-
❌ 缺点:
- 需要服务器配合配置。
- 在某些老旧浏览器上可能不兼容(但大多数项目已不再考虑)。
三、Hash 模式 vs History 模式对比总结
| 对比项 | Hash 模式 | History 模式 |
|---|---|---|
| URL 形式 | 含 #,如 /#/home | 标准路径,如 /home |
| 是否需服务器配置 | 否 | 是 |
| 兼容性 | 极好(IE8+) | 需 HTML5 支持(IE10+) |
| SEO 支持 | 较差 | 更好 |
| 推荐场景 | 快速原型、内部工具 | 正式上线、SEO 友好项目 |
四、实际开发中的选择建议
✅ 使用 Hash 模式的推荐场景:
- 开发快速原型或演示页面。
- 不关心 SEO。
- 不想配置服务器。
- 需要兼容老旧浏览器。
✅ 使用 History 模式的推荐场景:
- 面向公众的产品级应用。
- 需要良好的 SEO 支持。
- 希望 URL 更加美观。
- 有能力进行服务器配置(如 Nginx、Apache、Node.js 等)。
五、React 中的使用示例(react-router-dom)
如果你使用 React,可以使用 react-router-dom 来简化路由管理。
安装
npm install react-router-dom
使用示例
import React from 'react';
import { BrowserRouter as Router, Routes, Route, Link } from 'react-router-dom';
function Home() {
return <h2>首页</h2>;
}
function About() {
return <h2>关于</h2>;
}
function App() {
return (
<Router>
<nav>
<Link to="/">首页</Link> |
<Link to="/about">关于</Link>
</nav>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
</Routes>
</Router>
);
}
export default App;
默认情况下,BrowserRouter 使用的是 History 模式。如果你想使用 Hash 模式,可以改用 HashRouter:
import { HashRouter as Router } from 'react-router-dom';