SPA 中的 Hash 和 History 模式

SPA 中的 Hash 和 History 模式

在单页应用(SPA)中,路由是核心功能之一。Hash 和 History 是两种主流的客户端路由实现方式,它们解决了在不刷新页面的情况下改变 URL 并渲染不同内容的问题。


📍 Hash 模式

工作原理

利用 URL 中的 hash (# 后面的部分)来模拟路由。当 hash 改变时,浏览器不会向服务器发送请求,也不会刷新页面,只会触发 hashchange 事件。

URL 示例

bash 复制代码
http://example.com/#/user/profile
                    ↑ 这部分是 hash

核心特点

  • # 后面的内容不会被发送到服务器 ,服务器收到的请求始终是 http://example.com/
  • 改变 window.location.hash 会改变 URL,但页面不刷新
  • 监听 window.onhashchange 事件来响应路由变化

简单实现

javascript 复制代码
class HashRouter {
  constructor(routes) {
    this.routes = routes;
    window.addEventListener('hashchange', () => this.render());
    window.addEventListener('DOMContentLoaded', () => this.render());
  }
  
  render() {
    const hash = window.location.hash.slice(1) || '/';
    const component = this.routes[hash];
    if (component) {
      document.getElementById('app').innerHTML = component;
    }
  }
}

// 使用
const router = new HashRouter({
  '/': '<h1>首页</h1>',
  '/about': '<h1>关于</h1>',
  '/user': '<h1>个人中心</h1>'
});

📍 History 模式

工作原理

利用 HTML5 新增的 History API (pushState 和 replaceState)来操作浏览器历史记录栈,结合 popstate 事件实现路由切换。

URL 示例

bash 复制代码
http://example.com/user/profile
                    ↑ 没有 # 符号,看起来像正常 URL

核心 API

API 作用
pushState(state, title, url) 添加一条历史记录,改变 URL 但不刷新页面
replaceState(state, title, url) 替换当前历史记录,改变 URL 但不刷新页面
popstate 事件 监听浏览器前进/后退按钮操作

简单实现

javascript 复制代码
class HistoryRouter {
  constructor(routes) {
    this.routes = routes;
    
    // 监听前进后退
    window.addEventListener('popstate', () => this.render());
    
    // 拦截链接点击
    document.body.addEventListener('click', (e) => {
      if (e.target.tagName === 'A' && e.target.getAttribute('href')) {
        e.preventDefault();
        const url = e.target.getAttribute('href');
        this.navigateTo(url);
      }
    });
    
    this.render();
  }
  
  navigateTo(url) {
    history.pushState(null, null, url);
    this.render();
  }
  
  render() {
    const path = window.location.pathname;
    const component = this.routes[path] || this.routes['/404'];
    if (component) {
      document.getElementById('app').innerHTML = component;
    }
  }
}

📊 详细对比

对比维度 Hash 模式 History 模式
URL 外观 包含 # 符号,不够美观 正常的 URL,干净美观
服务器配置 不需要特殊配置,所有请求都指向同一 HTML 需要配置(见下文),否则刷新会 404
路由传参 只能通过 hash 传递,长度受限 可以使用 query string,更灵活
锚点滚动 冲突!# 既用于路由也用于页面内锚点 无此问题,可以正常使用锚点
SEO 友好度 ❌ 较差,搜索引擎可能忽略 # 后的内容 ✅ 友好,URL 真实且可被抓取
浏览器兼容性 ✅ 所有浏览器,包括 IE8+ ⚠️ IE10+,需要 HTML5 History API
Hash 变化 触发 hashchange 事件 触发 popstate 事件

⚠️ History 模式的关键:服务器配置

为什么需要配置?

在 History 模式下,直接访问 http://example.com/user/profile 时,浏览器会向服务器请求 /user/profile 这个路径。由于这是 SPA 的路由,服务器上并没有这个实际文件,因此会返回 404 Not Found。

解决方案:兜底配置

将所有请求都重定向到 index.html,让 SPA 路由接管后续处理。

Nginx 配置

nginx 复制代码
location / {
  try_files $uri $uri/ /index.html;
}

Apache 配置 (.htaccess)

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>

Node.js (Express) 配置

javascript 复制代码
const express = require('express');
const path = require('path');
const app = express();

// 静态文件服务
app.use(express.static(path.join(__dirname, 'dist')));

// 所有路由都返回 index.html
app.get('*', (req, res) => {
  res.sendFile(path.join(__dirname, 'dist', 'index.html'));
});

🎯 如何选择?

选择 Hash 模式的场景

  • 小型项目、内部工具、管理后台(不关心 SEO)
  • 没有服务器配置权限(如使用 GitHub Pages、纯静态托管)
  • 需要兼容老旧浏览器(IE 8/9)
  • 临时演示或快速原型

选择 History 模式的场景

  • 需要 SEO 的公开项目(企业官网、博客、电商)
  • 追求更干净的 URL,符合现代 Web 标准
  • 有权限配置服务器(Nginx/Apache/Node.js)
  • 需要复杂的 query 参数传递 (?id=123&name=test)

💡 主流框架中的使用

Vue Router

javascript 复制代码
// Vue 3 示例
import { createRouter, createWebHashHistory, createWebHistory } from 'vue-router'

// Hash 模式
const router = createRouter({
  history: createWebHashHistory(),
  routes: [...]
})

// History 模式
const router = createRouter({
  history: createWebHistory(),
  routes: [...]
})

React Router

jsx 复制代码
// React Router v6
import { HashRouter, BrowserRouter } from 'react-router-dom'

// Hash 模式
<HashRouter>
  <App />
</HashRouter>

// History 模式 (BrowserRouter)
<BrowserRouter>
  <App />
</BrowserRouter>

🔧 常见问题

Q: History 模式下刷新页面 404 怎么解决? A: 配置服务器将所有请求指向 index.html(参考上面的配置示例)。

Q: Hash 模式能用于生产环境吗? A: 可以,许多知名的管理后台(如 Ant Design Pro)默认就使用 Hash 模式,简单可靠。

Q: 两种模式可以混用吗? A: 不建议。在一个项目中应统一使用一种模式。

Q: 锚点(页面内跳转)在 Hash 模式下怎么办? A: 可以使用 element.scrollIntoView() 来替代传统锚点,或者采用其他命名方案避免冲突。

相关推荐
卸任5 分钟前
Vibe coding工具使用心得
前端·ai编程
brien91 小时前
为什么模型标称 1M,Google Antigravity 聊到 256K 却突然失忆?我们给它做了个原生行内监控圆环
前端
修炼的dance1 小时前
录屏关键帧间隔多少合适?六档实测,2秒到10秒小了75.6%
前端
Lank_M1 小时前
关键帧从1秒拉到10秒,录屏小87.3%,摄像头小10.8%
前端
补码缩写1 小时前
用 ffprobe 拆录屏,八成字节在 29 个关键帧上
前端
百思不得小李1 小时前
一个复杂的动态表单是如何实现的
前端·设计模式
陈随易1 小时前
Cmder 停更 17 个月,2.0 支持 Windows Terminal
前端·后端·程序员
风骏时光牛马2 小时前
前沿视界
前端
kyriewen2 小时前
我扫了 814 个依赖,97 个还敢要 Node 10
前端·javascript·程序员
陈随易2 小时前
3000 万人在用的翻译插件,重写一遍就是一门生意
前端·后端·程序员