Vue Router 提供了三种路由模式:hash、history 和 memory。其中前两种是浏览器端单页应用(SPA)最常用的模式,它们核心要做的是同一件事:让 URL 变化时,不向服务器重新请求整个页面,而是只切换前端的视图组件。它们的实现完全依赖浏览器原生 API,理解这些底层机制,能帮你更好地处理路由相关的 bug、配置服务器,甚至手动实现一个简易路由器。
hash 模式:基于锚点的兼容方案
hash 模式下的 URL 格式为:https://example.com/#/user/profile。其中 # 以及它后面的部分叫做哈希值(hash),它原本的作用是用于页内锚点定位,但前端路由系统利用它来实现无刷新的页面切换。
底层原理
- 浏览器不会将 hash 变化发送给服务器。改变
#后面的内容,浏览器只认为是当前页面内部的位置跳转,不会触发页面重新加载。 - 当 hash 变化时,浏览器会触发
hashchange事件。我们只需要监听这个事件,从中读取新的 hash,然后渲染对应的组件即可。 - 初始加载时,读取
window.location.hash来决定渲染哪个组件。
极简实现
下面是用原生 JavaScript 手动实现一个 hash 路由器的核心逻辑:
class HashRouter {
constructor(routes) {
this.routes = routes; // 路由表 { '/home': HomeComponent, ... }
this.currentComponent = null;
window.addEventListener('hashchange', () => this.onHashChange());
// 首次加载,手动触发匹配
this.onHashChange();
}
onHashChange() {
const hash = window.location.hash.slice(1) || '/'; // 去掉 #
const component = this.routes[hash];
if (component) {
// 模拟渲染:实际应用中会通知 Vue 更新 router-view 中的组件
document.getElementById('app').innerHTML = component();
}
}
push(path) {
window.location.hash = path; // 修改 hash 会触发 hashchange
}
}
// 使用示例
const routes = {
'/': () => '<h1>首页</h1>',
'/about': () => '<h1>关于我们</h1>'
};
const router = new HashRouter(routes);
- 兼容性:
hashchange事件兼容所有浏览器,包括 IE8。这使得 hash 模式成为历史遗留项目或要求极致兼容场景的可靠选择。 - 优点:无需任何服务器特殊配置,部署到任何静态服务器都能直接运行。
- 缺点:URL 中带有
#,不够美观,对 SEO 也不友好(虽然现代爬虫已经有所改善,但#仍然会让 URL 显得不那么“标准”)。
history 模式:基于 HTML5 History API 的现代方案
history 模式下的 URL 格式为:https://example.com/user/profile。看上去和普通的服务端路由完全一样,但它在跳转时同样不重新加载页面。
底层原理
- 依赖于 HTML5 引入的
history.pushState()和history.replaceState()方法。这两个方法可以在不刷新页面的情况下,改变浏览器地址栏的 URL,并同时在会话历史中添加一条记录。 - 当前进/后退(用户点击浏览器按钮)时,会触发
popstate事件。注意:pushState和replaceState调用时不会触发popstate,只有浏览器的后退/前进等操作才会触发。因此需要在全局点击拦截或编程式导航里主动更新视图。 - 初始加载时,读取
window.location.pathname来决定渲染哪个组件。
极简实现
class HistoryRouter {
constructor(routes) {
this.routes = routes;
this.currentComponent = null;
// 监听浏览器前进/后退
window.addEventListener('popstate', () => this.onPopState());
this.onPopState(); // 首次加载
}
onPopState() {
const path = window.location.pathname;
const component = this.routes[path] || this.routes['*'];
if (component) {
document.getElementById('app').innerHTML = component();
}
}
push(path) {
history.pushState(null, '', path); // 改变 URL,不刷新页面
this.onPopState(); // 手动触发渲染更新
}
replace(path) {
history.replaceState(null, '', path);
this.onPopState();
}
}
pushState第一参数:可以存放一个状态对象,通过history.state获取,用于在返回时恢复页面状态而不必重新加载数据。
关键制约:服务器配置
因为 history 模式下的 URL 看起来是真实的文件路径(如 /user/profile),当用户直接访问这个 URL 或刷新页面时,浏览器会向服务器请求这个路径。如果服务器没有对该路径的配置,通常会返回 404 错误。
必须的服务器配置示例:
- Nginx
location / {
try_files $uri $uri/ /index.html;
}
含义:先尝试找请求的文件 $uri,找不到找目录 $uri/,再找不到就返回 index.html,由前端路由接管。
- Node.js (Express)
const history = require('connect-history-api-fallback');
app.use(history());
- Vite 开发服务器内置了 history fallback,所以本地开发时刷新不会有 404 问题。
如果不进行该配置,刷新页面就会看到白屏和 404 错误。这就是 history 模式部署时最常见的坑。
hash 与 history 对比
| 特性 | hash 模式 | history 模式 |
|------|-----------|-------------|
| URL 外观 | 带有 #,如 /#/about | 干净,如 /about |
| SEO | 较差,# 后内容通常不被搜索引擎收录(但部分引擎已改进) | 良好,与普通 URL 无异 |
| 兼容性 | 极好(IE8+) | 需 HTML5 支持(IE10+) |
| 服务器配置 | 无需特殊配置 | 必须配置后端 fallback |
| 实现原理 | hashchange 事件 | pushState + popstate 事件 |
Vue Router 中如何切换模式
在创建 router 实例时,通过 history 选项指定:
import { createRouter, createWebHashHistory, createWebHistory } from 'vue-router'
// hash 模式
const router = createRouter({
history: createWebHashHistory(),
routes: [...]
})
// history 模式
const router = createRouter({
history: createWebHistory(),
routes: [...]
})
Vue Router 在底层就是根据你选择的 history 类型,采取上面描述的最原生事件监听机制,并结合 Vue 的响应式系统,来动态切换 router-view 中渲染的组件。理解这一点之后,排查“为什么路由跳转没反应”、“刷新后 404”等问题就变得有据可循了。