Vue Router 路由原理
“Vue Router 原理”不是只讲 hash 和 history。如果只答浏览器 API,会显得太浅;如果只答守卫,也会偏题。
更完整的答法应该把它拆成四层:
- URL 变化怎么被感知
- 路由表怎么匹配到组件
- 匹配结果怎么驱动视图更新
- 导航过程中怎么插入守卫、懒加载和滚动控制
本文默认以 Vue Router 4 + Vue 3 为主,Vue Router 3 的核心思路相同。
面试速答(30 秒版 TL;DR)
- Vue Router 本质是:监听 URL 变化 -> 匹配路由记录 -> 更新响应式路由状态 -> 让
router-view重新渲染对应组件树。 - 前端路由不是“没有路由”,而是把“路由匹配和页面切换”从服务端搬到了客户端。
- 两种常见模式:
hash:监听hashchange,URL 带#history:基于pushState/replaceState/popstate,URL 更干净,但服务端要兜底
router-link负责导航触发,router-view负责根据当前路由记录渲染组件。- 进阶加分点:导航守卫、动态路由、路由懒加载、滚动行为、本质上都挂在这条导航流水线里。
先记主链路:导航触发 -> URL 变化 -> history/hash 监听 -> matcher 匹配 -> currentRoute 更新 -> router-view 重渲染。
一、先搞清楚:什么叫前端路由
传统多页应用里,路由主要由服务端负责:
- 浏览器请求
/about - 服务端返回新的 HTML
- 浏览器整页刷新
单页应用里,首次只拿一个 HTML 壳子,后续路径变化主要由前端接管:
- URL 变了
- 框架拦截并识别这个地址
- 匹配出应该显示哪个页面组件
- 局部更新,而不是整页重载
所以前端路由的关键,不是“页面真的跳了”,而是 URL 变了,但页面内容由 JS 在同一个文档里切换。
二、Vue Router 的核心组成
1. 路由表
开发者会先声明一份路由配置:
const routes = [
{ path: '/', component: Home },
{ path: '/users/:id', component: UserDetail },
]
这份配置会被 Router 内部处理成可匹配的数据结构。
2. History 实现层
Vue Router 会根据模式选择不同 history 实现:
createWebHashHistory()createWebHistory()createMemoryHistory()(SSR / 测试更常见)
这一层的职责,是把“浏览器地址变化”抽象成统一导航接口。
3. Matcher 匹配器
当 URL 变成 /users/123 时,Router 需要知道它命中了哪条路由:
- 路径是否匹配
- 动态参数是什么
- 嵌套路由链路是什么
- 该渲染哪些组件记录
这部分就是 matcher 的职责。
4. 当前路由状态 currentRoute
Vue Router 内部维护一个响应式路由对象,大致包含:
pathfullPathparamsqueryhashmatchedmeta
一旦它变化,依赖它的组件就会更新。
5. 渲染出口 router-view
router-view 不是魔法,它本质上就是:
- 读取当前命中的路由记录
- 找到当前层级对应的组件
- 渲染出来
嵌套路由时,会由多个 router-view 按深度逐层渲染。
三、Hash 和 History 到底怎么工作
1. Hash 模式
URL 类似:
https://site.com/#/users/1
特点:
#后面的内容变化不会触发浏览器向服务端重新请求页面- 可以监听
hashchange - 部署最简单,服务端不用额外兜底
缺点:
- URL 不够干净
- SEO、分享链接体验相对弱一点
2. History 模式
核心 API:
history.pushStatehistory.replaceStatepopstate
URL 会变成:
https://site.com/users/1
优点:
- 地址更像传统站点
- 用户体验和可读性更好
缺点:
- 用户直接刷新
/users/1时,服务器必须把这个地址也回退到你的 SPA 入口页,否则就会 404
这就是为什么面试里经常会问:History 模式为什么需要服务端配合。
四、一次导航到底发生了什么
以 router.push('/users/1') 为例,可以这样理解:
1. 发起导航
- 代码调用
router.push - 或者用户点击
<router-link>
2. 规范化目标地址
Router 会把目标转换成标准 location,处理:
- 路径
- query
- hash
- 命名路由
- params
3. 路由匹配
Router 用 matcher 找到:
- 命中的路由记录
- 动态参数结果
- 从父到子的嵌套链路
4. 执行导航守卫
例如:
- 组件离开守卫
- 全局前置守卫
- 路由独享守卫
- 组件进入守卫
- 全局解析守卫
如果中间被取消或重定向,导航流程就会改道。
5. 确认导航并更新当前路由状态
当导航通过后,内部会更新 currentRoute。
6. router-view 响应式重渲染
因为 router-view 依赖 currentRoute,所以它会重新选择对应组件并渲染。
五、router-link 和 router-view 分别做什么
1. router-link
可以把它理解成“增强版 a 标签”:
- 负责生成目标地址
- 点击时阻止默认整页跳转
- 改成调用
router.push/replace - 自动处理激活态 class
所以它不是单纯渲染标签,而是“导航触发器”。
2. router-view
可以把它理解成“动态组件插槽”:
- 当前 URL 匹配到哪条记录
- 它就渲染哪条记录对应的组件
- 如果是嵌套路由,就继续往下层
router-view传递深度
这也是为什么很多人会把 Vue Router 概括成:
URL 决定状态,
router-view决定显示。
六、动态路由为什么能匹配参数
例如:
{ path: '/users/:id', component: UserDetail }
内部会把路径规则编译成可匹配结构。匹配到 /users/123 后:
- 路由记录命中
params.id === '123'
如果跳转的是 /users/456,可能不会销毁组件,而是复用同一个组件实例,只更新参数。所以这类场景常配合:
beforeRouteUpdateonBeforeRouteUpdatewatch(() => route.params.id)
七、为什么路由切换时页面不会整页刷新
因为 Vue Router 并不是让浏览器重新加载整个文档,而是:
- 拦截导航
- 修改历史记录或 hash
- 更新前端维护的路由状态
- 触发组件树局部重渲染
真正的“页面切换感”,其实来自组件树替换,而不是浏览器重新加载 HTML。
八、典型题 & 标准答法
Q1:Vue Router 的核心原理是什么?
答:核心是监听 URL 变化,再根据路由表做匹配,得到当前应该渲染的路由记录,然后把结果写入响应式的 currentRoute,最后由 router-view 根据这个状态渲染对应组件。也就是说,它本质上是“URL 状态机 + 组件渲染出口”。
Q2:Hash 和 History 的根本区别是什么?
答:Hash 依赖 # 和 hashchange,不会触发服务端重新处理路径;History 依赖 pushState/replaceState/popstate,URL 更干净,但刷新深路径时需要服务端把请求兜回 SPA 入口页。
Q3:为什么 router-view 能跟着路径变化自动切换页面?
答:因为它依赖的是内部响应式 currentRoute。一旦导航确认并更新了当前路由状态,router-view 会重新取出对应层级的路由组件并渲染。
九、易错点 / 坑
- 把路由原理只答成
hash和history,忽略了 matcher 和router-view。 - 以为
router-link就是普通a标签。它会拦截默认行为并走 Router API。 - 以为路径变了就一定会销毁组件。动态参数变化场景经常是组件复用。
- 只会说前端路由“不刷新页面”,但说不清为什么不刷新。
速记要点
- 路由原理 = 监听 URL + 匹配路由 + 更新响应式状态 + 渲染组件
router-link负责触发导航router-view负责显示当前命中组件- Hash 简单,History 干净但要服务端兜底
- 守卫、懒加载、滚动行为,都是导航流程上的扩展能力