SPA 中的 Hash 和 History 模式

SPA 中的 Hash 和 History 模式

在单页应用(SPA)中,路由是核心功能之一。HashHistory 是两种主流的客户端路由实现方式,它们解决了在不刷新页面的情况下改变 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 APIpushStatereplaceState)来操作浏览器历史记录栈,结合 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() 来替代传统锚点,或者采用其他命名方案避免冲突。

相关推荐
用户4445543654261 小时前
AndroidAutoSize使用时遇到的特麻烦bug
前端
茉莉玫瑰花茶2 小时前
LangGraph 入门教程:构建 AI 工作流 [ 案例三 ]
前端·人工智能·python
scan7242 小时前
pydantic格式输出
服务器·前端·javascript
ZC跨境爬虫2 小时前
跟着MDN学HTML_day44:(ProcessingInstruction接口)
前端·javascript·ui·html·媒体
CODE202203182 小时前
promptfoo自定义prompt生成器
java·前端·prompt
222you2 小时前
Claude Code接入DeepSeek-v4模型
java·服务器·前端
轻口味2 小时前
AI 时代全栈开发破局:TypeScript 生态实战,从入门到部署一站式通关
前端·mongodb·docker·ai·typescript·react·next.js
ZC跨境爬虫2 小时前
跟着MDN学HTML_day_45:(EventTarget接口)
前端·javascript·ui·html·媒体
漂移的电子3 小时前
【el-tree】外层多选,某个属性内层单选
前端·javascript·vue.js