导航
| 模式 | URL 形态 | 底层依赖 | 是否需要后端配合 |
|---|---|---|---|
| hash | /#/user/1 | location.hash + hashchange | ❌ 不需要 |
| history | /user/1 | history.pushState + popstate | ✅ 需要 |
vue-router 本质:监听 URL 的变化 → 匹配路由表 → 渲染对应组件
location.hash 读写hashchange 监听<!DOCTYPE html>
<html lang="en">
<body>
<button id="myBtn">按钮</button>
<script>
const btn = document.getElementById('myBtn');
window.addEventListener('DOMContentLoaded', () => {
console.log(location.hash);
});
btn.addEventListener('click', () => {
location.hash = '#/lance';
});
window.addEventListener('**hashchange**', () => {
console.log(location.hash);
})
</script>
</body>
</html>
function setupHashListener() {
window.addEventListener('hashchange', () => {
const path = location.hash.slice(1) // "/user/1"
router.match(path)
router.updateView()
})
}
流程图:
修改 hash
↓
触发 hashchange
↓
解析 hash 路径
↓
路由匹配
↓
组件重新渲染
因为:
浏览器规范里明确:
改 hash ≠ 页面跳转
router.push() 或 router.replace() 时,Vue Router 实际上调用了浏览器的 history.pushState() 或 history.replaceState()。pushState 和 replaceState 仅修改浏览器 URL,并将新的路由记录存入历史栈,但不会触发页面重载。popstate 事件。window.location.pathname)查找对应的组件并渲染。pushState/replaceState 不触发 popstate,Vue Router 会拦截这些方法的调用,并在内部手动触发路由切换逻辑,以确保路由始终与 URL 同步。/users/1) 404 的问题,需要服务器配置将所有不存在的请求都重定向到应用的入口文件 (如 index.html),由前端 Vue Router 接管路由。<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<button id="myBtn">按钮</button>
<script>
const btn = document.getElementById('myBtn');
window.addEventListener('DOMContentLoaded', () => {
console.log(location.pathname);
});
btn.addEventListener('click', () => {
const state = { name: 'user' };
history.pushState(state, '', 'user'); // 此处不会触发下方 popstate 监听
// state, title, url
});
window.addEventListener('**popstate**', () => {
console.log(location.pathname);
});
</script>
</body>
</html>
“劫持”了 history 方法
const originalPush = history.pushState
history.pushState = function (...args) {
originalPush.apply(history, args)
router.notify() // 手动通知路由变化
}
流程图:
浏览器前进:
router.push('/user/1')
↓
history.pushState()
↓
vue-router 手动触发路由更新
↓
组件重新渲染
浏览器后退:
点击后退
↓
popstate
↓
vue-router 监听
↓
路由更新
问题场景
用户访问:
<http://example.com/user/1>
浏览器行为:
正确做法(后端兜底)
location / {
try_files $uri $uri/ /index.html;
}
所有路径都返回 SPA 的 index.html 再由 vue-router 接管
class VueRouterinstall(),Vue.use() 时需要调用
install 时,会把 Vue 构造函数传给我们Vue.mixin -> beforeCreate 中:
this.$options.router,也就是 new VueRouter 实例);找到后调用 init 方法开始执行window.addEventListener => DOMContentLoaded、hashchange)router-link、router-viewmy-router/index.js
let Vue;
class VueRouter {
constructor(options) {
this.$options = options;
this.routeMap = {};
this.vm = new Vue({
data() {
return {
currentPath: '/'
}
}
});
}
init() {
// 1. 监听hash值变化
this.bindEvent();
// 2. 创建路由映射表
this.createHashMap();
// console.log(this.routeMap);
// {/: {path: '/', name: 'Home', component: {…}}, /about: {path: '/about', name: 'About', component: {...}}
// 3. 注册全局路由组件 router-link、router-view
this.initRouteComponent();
}
bindEvent() {
// 页面加载完监听
window.addEventListener('DOMContentLoaded', this.handleHashChange.bind(this), false);
// hash值变化后监听
window.addEventListener('hashchange', this.handleHashChange.bind(this), false);
}
handleHashChange() {
console.log('handleHashChange-hash:', window.location.hash);
const hash = this.getHashValue();
// vm实例下的属性发生改变,会导致重新渲染
this.vm.currentPath = hash;
}
// 获取hash值
getHashValue() {
// console.log("getHashValue-hash:", window.location.hash.slice(1));
return window.location.hash.slice(1) || '/';
}
// 创建路由映射表
createHashMap() {
// 把 routes数组 转 hash 方式存储
this.$options.routes.forEach(item => {
this.routeMap[item.path] = item;
});
}
// 初始化全局路由组件
initRouteComponent() {
Vue.component('router-view', {
render: h => {
const component = this.routeMap[this.vm.currentPath].component;
// return h('h1', 'Hello JS++');
return h(component);
}
});
Vue.component('router-link', {
props: {
to: String,
},
render(h) {
// h函数: 标签名, 属性集合
// this.$slots.default: 拿到插槽内容
return h('a', {
attrs: {
href: '#' + this.to
}
}, this.$slots.default);
}
});
}
// install 得是个静态方法
// 因为Vue需要调用install (Vue.install)
// 但调用时不需要通过new来实例化
// Vue在调用install时,会传入参数:Vue构造函数
static install(_Vue) {
Vue = _Vue;
// console.log(Vue);
// console.log('vue router install');
Vue.mixin({
beforeCreate() {
// 这里会打印两次,因为有俩vue实例(main.js中new Vue,App.vue中的vue),vue会给它们分别添加 beforeCreate 钩子
// console.log('mixin里的一些数据');
// 打印后会发现,只有根组件 root 才有 router 实例,也就是 new VueRouter 这个实例
// 所以后边我们需要判断,当有router实例时,才执行上边的 init 方法
console.log('当前vue实例:', this.$options.name, this.$options.router);
// 找到根Vue实例,然后取出里边的router实例,并执行init方法
if (this.$options.router) {
this.$options.router.init();
}
}
});
}
}
export default VueRouter;