跳到主要内容

Vue Router 路由原理

“Vue Router 原理”不是只讲 hashhistory。如果只答浏览器 API,会显得太浅;如果只答守卫,也会偏题。

更完整的答法应该把它拆成四层:

  1. URL 变化怎么被感知
  2. 路由表怎么匹配到组件
  3. 匹配结果怎么驱动视图更新
  4. 导航过程中怎么插入守卫、懒加载和滚动控制

本文默认以 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 内部维护一个响应式路由对象,大致包含:

  • path
  • fullPath
  • params
  • query
  • hash
  • matched
  • meta

一旦它变化,依赖它的组件就会更新。

5. 渲染出口 router-view

router-view 不是魔法,它本质上就是:

  • 读取当前命中的路由记录
  • 找到当前层级对应的组件
  • 渲染出来

嵌套路由时,会由多个 router-view 按深度逐层渲染。

三、Hash 和 History 到底怎么工作

1. Hash 模式

URL 类似:

https://site.com/#/users/1

特点:

  • # 后面的内容变化不会触发浏览器向服务端重新请求页面
  • 可以监听 hashchange
  • 部署最简单,服务端不用额外兜底

缺点:

  • URL 不够干净
  • SEO、分享链接体验相对弱一点

2. History 模式

核心 API:

  • history.pushState
  • history.replaceState
  • popstate

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,所以它会重新选择对应组件并渲染。

可以把它理解成“增强版 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,可能不会销毁组件,而是复用同一个组件实例,只更新参数。所以这类场景常配合:

  • beforeRouteUpdate
  • onBeforeRouteUpdate
  • watch(() => route.params.id)

七、为什么路由切换时页面不会整页刷新

因为 Vue Router 并不是让浏览器重新加载整个文档,而是:

  1. 拦截导航
  2. 修改历史记录或 hash
  3. 更新前端维护的路由状态
  4. 触发组件树局部重渲染

真正的“页面切换感”,其实来自组件树替换,而不是浏览器重新加载 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 会重新取出对应层级的路由组件并渲染。

九、易错点 / 坑

  • 把路由原理只答成 hashhistory,忽略了 matcher 和 router-view
  • 以为 router-link 就是普通 a 标签。它会拦截默认行为并走 Router API。
  • 以为路径变了就一定会销毁组件。动态参数变化场景经常是组件复用。
  • 只会说前端路由“不刷新页面”,但说不清为什么不刷新。

速记要点

  • 路由原理 = 监听 URL + 匹配路由 + 更新响应式状态 + 渲染组件
  • router-link 负责触发导航
  • router-view 负责显示当前命中组件
  • Hash 简单,History 干净但要服务端兜底
  • 守卫、懒加载、滚动行为,都是导航流程上的扩展能力