当你访问 a.com/page1 跳到 a.com/page2 时:
Vue Router 的出现,就是为了在 “不刷新页面” 的前提下,模拟出 “换页” 的效果。
它建立了一张映射表:
| URL 路径 | 对应的 Vue 组件 |
| :--- | :--- |
| /home | Home.vue |
| /user/:id | User.vue |
它的工作流程是:
实现了三个维度的“桥接”:
它封装了浏览器的原生 API。
这是路由器的“大脑”。
如果保持原始的嵌套树结构,每次 URL 变化都要进行一次递归搜索,在大型应用中(比如包含数百个路由),性能开销将不可接受
当你执行 createRouter 时,内部会调用一个名为 createRouterMatcher 的函数。它会遍历你定义的嵌套 routes 数组,递归地生成一个扁平化的结构
为了实现 $O(1)$ 或极高效率的查询,它会维护三张核心表:(Vue Router4)
matcherMap:以 name 为 Key 的映射表。
pathMatchers:一个包含所有路由记录的数组(用于路径正则匹配)。
matchers:所有路由记录的集合
扁平化前:
const routes = [
{
path: '/algorithm',
component: Parent,
children: [
{ name: 'Detail', path: 'detail/:id', component: Detail }
]
}
];扁平化后:
// 扁平化后的 Matchers 数组
[
{
path: '/algorithm',
record: { component: Parent, children: [...] },
re: /^\/algorithm\/?$/i, // 自动生成的正则表达式
score: [10, 0], // 用于排序的权重分
},
{
path: '/algorithm/detail/:id',
name: 'Detail',
record: { component: Detail },
re: /^\/algorithm\/detail\/([^/]+?)\/?$/i,
score: [10, 10, 5], // 动态参数权重略低于静态路径
parent: { ... } // 依然保留对父级的引用,以便渲染嵌套视图
}
]这是 Vue Router 区别于其他框架路由的关键。
原理: 利用 URL 的 # 符号,# 后面的变化不会触发浏览器请求服务器
// 监听 hashchange 事件
window.addEventListener('hashchange', () => {
// 获取当前 hash
const hash = window.location.hash.slice(1)
// 匹配路由,渲染组件
router.match(hash)
})| 优点 | 缺点 |
|---|---|
| 兼容性极好(支持IE9+) | URL中有#,不够美观 |
| 无需服务端配置 | SEO较差(爬虫不解析#后内容) |
| 不会导致404 | 锚点功能失效 |
原理: 利用 HTML5 的 History API(pushState(往历史栈添加一条新记录)、replaceState(替换当前历史记录)、popstate(浏览器前进后退))来管理路由。
// 1. 监听 popstate 事件(浏览器前进/后退)
window.addEventListener('popstate', () => {
const path = window.location.pathname
router.match(path)
})
// 2. pushState 和 replaceState 不触发事件,需要手动处理
function push(path) {
history.pushState(null, '', path) //状态对象 标题 新url
// 手动触发路由更新
router.match(path)
}
function replace(path) {
history.replaceState(null, '', path)
router.match(path)
}| 优点 | 缺点 |
|---|---|
| URL美观,无# | 需要服务端配置(否则刷新404) |
| SEO友好 | 兼容性稍差(IE10+) |
| 更接近原生体验 | 需要处理锚点冲突 |
需要在nginx配置:
location / {
try_files $uri $uri/ /index.html;
}router.push() 主动修改URL,然后事件监听用来响应“浏览器前进/后退”
class MiniRouter {
constructor(routes) {
this.routes = routes // 路由配置
this.currentPath = '/' // 当前路径
this._route = { current: null } // 响应式数据
// 1. 监听 popstate(处理浏览器前进/后退)
window.addEventListener('popstate', () => {
console.log('监听到popstate,URL变了')
// 获取新的URL
const newPath = window.location.pathname
// 手动更新页面
this.transitionTo(newPath)
})
// 2. 监听 hashchange(hash模式用)
window.addEventListener('hashchange', () => {
const newPath = window.location.hash.slice(1)
this.transitionTo(newPath)
})
}
// ⭐ 核心方法:路由跳转
push(path) {
console.log('主动跳转到:', path)
// 第一步:修改浏览器URL
// 注意:这里用的是 pushState,它不会触发 popstate!(history模式)
history.pushState(null, null, path)
// hash模式为 window.location.hash = path,会主动触发hashchange
// 第二步:手动更新页面内容
// 因为 pushState 不会自动触发任何事件,必须手动调用
this.transitionTo(path)
}
// 实际执行页面更新的方法
transitionTo(path) {
console.log('执行页面更新:', path)
// 匹配路由
const route = this.matchRoute(path)
// 更新响应式数据(触发 router-view 重新渲染)
this._route.current = route
// 更新当前路径
this.currentPath = path
}
matchRoute(path) {
return this.routes.find(route => route.path === path)
}
}
// 使用示例
const router = new MiniRouter([
{ path: '/', component: 'Home' },
{ path: '/about', component: 'About' }
])
// 主动跳转 - 调用 push
router.push('/about')
// 执行流程:
// 1. history.pushState() → 地址栏变成 /about
// 2. transitionTo() → 页面切换到 About 组件
// 用户点击后退 - 浏览器触发 popstate
// 执行流程:
// 1. 浏览器改变URL → 地址栏变成 /
// 2. 触发 popstate 事件
// 3. 事件回调里调用 transitionTo('/') → 页面切换到 Home 组件比如router.push(`/code/detail/${row.id}`)执行后,内部经历了哪些步骤
当你调用 router.push,首先接待你的是 History 记录管理器。
这是最硬核的一步。Router 需要弄清楚:这个字符串路径到底对应谁?
在 URL 真正改变之前,必须通过一系列的“安检”。Vue Router 4 将这些钩子函数串联成了一个 Promise 链。
底层秘密:只要其中任何一个钩子返回 false 或者 reject,整个 router.push 产生的 Promise 就会直接中断,地址栏和视图都不会有任何变化。
一旦所有守卫通过,Router 才会真正去动浏览器的东西。
原生限制:popstate 只有在用户点击后退、前进按钮,或者调用 history.back() 时才会触发。
Vue Router 的解决方案:它重写了跳转方法。当你调用 router.push,它在内部同时执行了“改地址栏”和“触发 Vue 组件渲染”两个任务
这是最后一步,也是最关键的一步:如何通知 Vue 更新界面?
正常加载:是在页面初始化时,通过 import Content from '...' 直接引入。这意味着无论用户是否访问该页面,组件代码都会被打包进 app.js(主包)中。
懒加载:使用 () => import('...') 这种动态导入语法。只有当用户 真正访问 那个路由时,浏览器才会去加载该组件对应的脚本
“路由懒加载本质上是一种**‘空间换时间’**的策略。
我们通过在磁盘上生成更多的分包文件(空间),换取了首屏加载时更少的网络传输量(时间)
