Vue Router 源码解析与实现原理
约 3092 字大约 10 分钟
布欧-Lewyon
2026-05-15
首页 › Vue › 路由与状态管理 › Vue Router 源码解析与实现原理
Vue Router 是 Vue 生态中最复杂的库之一。理解它的源码架构和设计模式,有助于写出更健壮的路由系统,也能从容面对面试中的深度问题。
总体架构
| 模块 | 职责 | 核心文件 (vue-router 源码) |
|---|---|---|
| Router | 顶层入口,协调各模块 | src/router.ts |
| Matcher | 路由注册与匹配 | src/matcher/ |
| History | URL 监听与同步 | src/history/ |
| Guards | 导航守卫队列管理 | src/navigationGuards.ts |
| Components | <router-view> / <router-link> | src/router/components/ |
| Composables | useRouter / useRoute / onBeforeRouteLeave | src/composables/ |
设计模式分析
Vue Router 内部使用了多种经典设计模式:
| 设计模式 | 使用位置 | 作用 |
|---|---|---|
| Plugin | app.use(router) | 将 Router 注入 Vue 全局 |
| Observer | useRoute() 响应式对象 | 路由变化时通知所有订阅组件 |
| Chain of Responsibility | 导航守卫队列 | 守卫依次执行,任一返回 false 则中断 |
| Strategy | createWebHistory / createWebHashHistory | 不同 history 策略封装 |
| Composite | 嵌套路由的 RouteRecord 树 | 路由记录形成父子层级树 |
| Factory | createRouter / createRouterMatcher | 创建复杂对象实例 |
Plugin 模式:Router 如何于 Vue 集成
Vue Router 通过 Vue 的插件机制注入全局能力,插件方法是 app.use(router) 执行的入口:
// 简化版 src/router.ts
export function createRouter(options) {
const router = {
// 插件 install 方法
install(app) {
// 1. 注册全局组件
app.component('RouterView', RouterView)
app.component('RouterLink', RouterLink)
// 2. 注入 $router / $route(Options API 兼容)
app.config.globalProperties.$router = router
app.config.globalProperties.$route = reactive(route)
// 3. 提供响应式 route 对象供 Composition API 使用
app.provide(routerKey, router)
app.provide(routeLocationKey, reactive(route))
// 4. 注册组件内守卫的 unmount 钩子
// 让 onBeforeRouteLeave / onBeforeRouteUpdate 生效
app.mixin({
beforeUnmount() {
// 清理组件内守卫
}
})
// 5. ✅ 核心:初始化导航
// 首次访问时,根据当前 URL 匹配路由,触发初始导航
if (isBrowser) {
const initialURL = window.location
// 根据 history 模式解析初始 URL
push(parseURL(initialURL))
}
}
}
// 初始化 matcher、history、guards 等
return router
}install 的核心步骤
- 注册全局组件 →
<router-view>和<router-link>在任何组件中可用 - 注入响应式 route → 通过
provide+reactive包装,使组件内useRoute()能响应路由变化 - 注册 mixin → 拦截
beforeUnmount钩子,清理组件内守卫 - 触发首次导航 → 初识化时推送到当前 URL,触发路由匹配和组件渲染
Strategy 模式:三种 History 策略
History 模块封装了对浏览器 URL 的监听与操作。三种策略实现同一接口,策略不同但用法一致:
接口定义
// 简化版 src/history/interface.ts
interface RouterHistory {
base: string // 基础路径
location: string // 当前 URL
state: HistoryState // 当前状态
push(to: string | HistoryLocation): void // 导航
replace(to: string | HistoryLocation): void
go(delta: number): void // 前进/后退
listen(callback: NavigationCallback): () => void // 监听变化
destroy(): void // 清理
}HTML5 History 实现
// 简化版 src/history/html5.ts
function createWebHistory(base = '') {
function push(to) {
const currentState = history.state || {}
// 使用 HTML5 pushState 改变 URL(不刷新页面)
history.pushState(currentState, '', to)
// 通知 Router 进行导航
// 触发导航守卫 → 匹配新路由 → 更新组件
changeLocation(to)
}
function listen(callback) {
// popstate 事件:用户点击浏览器的前进/后退按钮时触发
window.addEventListener('popstate', (event) => {
// 为什么用 popstate 而不是 hashchange?
// pushState/replaceState 不会触发 popstate,
// 但用户点击前进/后退会触发
callback({
location: window.location.pathname,
state: event.state
})
})
return () => window.removeEventListener('popstate', callback)
}
return { base, location, push, replace, go, listen }
}Hash History 实现
function createWebHashHistory(base = '') {
function push(to) {
// 改变 hash → 触发 hashchange 事件 → 自动通知
window.location.hash = to
}
function listen(callback) {
// hash 变化时触发
window.addEventListener('hashchange', () => {
callback({ location: window.location.hash.slice(1) })
})
}
return { base, location, push, replace, go, listen }
}Memory History(服务端渲染/测试)
function createMemoryHistory(base = '') {
const stack = ['']
let index = 0
function push(to) {
// 删除当前位置之后的历史
stack.length = index + 1
stack.push(to)
index++
callback({ location: to })
}
function go(delta) {
index = Math.max(0, Math.min(stack.length - 1, index + delta))
callback({ location: stack[index] })
}
return { base, location: () => stack[index], push, replace, go, listen }
}Composite 模式:RouteRecord 树
路由配置中的嵌套 children 会被扁平化为带层级的 RouteRecord 列表,形成一棵树:
interface RouteRecord {
path: string
name: string | null
components: Record<string, Component> // 命名视图
meta: Record<string, any> // 路由元信息
beforeEnter: NavigationGuard | null // 路由独享守卫
parent: RouteRecord | null // ⭐ 父级记录
children: RouteRecord[] // ⭐ 子级记录
// ...
}// 路由配置
const routes = [
{
path: '/users',
component: UserLayout,
children: [
{ path: '', component: UserList },
{ path: ':id', component: UserDetail }
]
}
]内部展开后:
路径正则编译
RouteRecord 中的 path 通过 path-to-regexp 库编译为正则表达式:
import { compile, pathToRegexp } from 'path-to-regexp'
// path: '/users/:id'
// 编译为正则 → 提取 params
const regex = pathToRegexp('/users/:id')
// regex = /^\/users\/([^/]+?)(?:\/)?$/i
// 匹配结果
regex.exec('/users/42')
// → ['/users/42', '42']
// 提取 params: { id: '42' }
// 生成路径
const toPath = compile('/users/:id')
toPath({ id: 42 }) // → '/users/42'RouteMatcher——路由匹配器
// 简化版 src/matcher/index.ts
function createRouterMatcher(routes) {
const matchers: RouteRecordMatcher[] = []
// 递归添加路由记录
function addRoute(route, parent) {
const normalized = normalizeRouteRecord(route, parent)
// path → regex
const { regex, keys } = pathToRegexp(normalized.path)
normalized.keys = keys
const matcher = {
record: normalized,
parent, // 父 match
children: [], // 子 match 列表
regex, // 编译后的正则
path: normalized.path
}
// 记录到全局列表
matchers.push(matcher)
// 递归处理子路由
if (route.children) {
route.children.forEach(child => addRoute(child, matcher))
}
}
// 初始化所有路由
routes.forEach(route => addRoute(route, null))
// ⭐ 核心:根据路径匹配路由
function resolve(path) {
const matched = []
// 遍历所有 matcher,找到匹配的
for (const matcher of matchers) {
const match = matcher.regex.exec(path)
if (match) {
// 从当前 matcher 向上回溯到根
// 收集完整的组件层级链
let current = matcher
const chain = []
while (current) {
chain.unshift(current.record)
current = current.parent
}
// chain = [parent, child, grandchild]
// 对应 <router-view> 嵌套层次
return {
route: {
matched: chain, // ⭐ 组件链
params: extractParams(matcher.keys, match),
path: path,
name: matcher.record.name
}
}
}
}
return null // 404
}
return { addRoute, resolve, getRoutes, removeRoute }
}Observer 模式:响应式路由系统
Vue Router 通过 reactive + provide 让路由变化自动通知所有组件:
// 创建响应式的 route 对象
const currentRoute = reactive({
path: '/',
name: null,
params: {},
query: {},
matched: [],
meta: {},
hash: '',
fullPath: '/'
})
// 在 install 中 provide
app.provide(routeLocationKey, currentRoute)
// useRoute() 就是 inject 这个响应式对象
function useRoute() {
return inject(routeLocationKey)
}关键设计:currentRoute 是响应式对象。当导航完成时更新它,所有 inject 了它的组件自动重新渲染。不需要手动通知。
与 Vue 响应式系统的交互
Chain of Responsibility 模式:导航守卫
导航守卫链条是 Chain of Responsibility(责任链)设计模式的经典应用。每个守卫是一个"处理器",按顺序执行,任一处理器返回 false 就中断整个导航。
守卫队列执行器
// 简化版 navigationGuards.ts
function navigate(to, from, guards) {
const queue = [
// 1. 失活组件的 onBeforeRouteLeave
...extractLeaveGuards(from.matched),
// 2. 全局 beforeEach
...guards.beforeEach,
// 3. 路由独享 beforeEnter
...extractBeforeEnter(to.matched),
// 4. 新路由组件的 onBeforeRouteUpdate
...extractUpdateGuards(from.matched, to.matched),
// 5. 全局 beforeResolve
...guards.beforeResolve,
]
// ⭐ 链式执行:前一守卫 resolve 后才执行下一守卫
return runQueue(queue, (guard, next) => {
try {
const result = guard(to, from)
if (result === false || result instanceof Error) {
next(result) // 中断导航
} else if (typeof result === 'string' || typeof result?.path === 'string') {
next(result) // 重定向
} else {
next() // 继续下一个守卫
}
} catch (err) {
next(err)
}
})
}
// ⭐ 队列运行器——手动串联异步守卫
function runQueue(queue, iterator, callback) {
let step = (index) => {
if (index >= queue.length || queue.length === 0) {
callback() // 所有守卫通过
return
}
const guard = queue[index]
iterator(guard, (result) => {
if (result === false) {
callback(result) // 中断!
} else if (result instanceof Error || typeof result === 'string' || result?.path) {
callback(result) // 重定向
} else {
step(index + 1) // 继续下一个守卫
}
})
}
step(0)
}守卫返回值处理
async function navigate(to, from, guards) {
const result = await runGuardQueue(guardQueue)
guard(next) === false
? cancelNavigation() // ❌ 取消
: guard(next) === '/login' || { path: '/login' }
? redirect(to) // ➡️ 重定向
: guard(next) === true || undefined
? continueNavigation() // ✅ 继续<router-view> 的实现
<router-view> 是路由系统的"出口"。它的核心是从当前 matched 路由中取出对应层级的组件并渲染:
// 简化版 src/router/RouterView.ts
const RouterView = defineComponent({
name: 'RouterView',
props: { name: { type: String, default: 'default' } },
setup(props, { attrs, slots }) {
// inject 响应式 route 对象
const route = inject(routeLocationKey)
// inject 当前渲染深度(用于处理嵌套路由)
const depth = inject(viewDepthKey, 0)
const injectedDepth = ref(depth)
// provide 下一层深度
provide(viewDepthKey, ref(depth + 1))
// ⭐ 核心:根据深度从 route.matched 中取组件
const matchedRouteRef = computed(() => {
const matched = route.value.matched[injectedDepth.value]
return matched?.components[props.name] || matched?.components.default
})
return () => {
const matchedComponent = matchedRouteRef.value
if (!matchedComponent) {
// 没有匹配到组件 → 渲染插槽 fallback 或空
return slots.default?.() || h('div')
}
// 渲染匹配的组件,传递 route 的 params 等
return h(matchedComponent, {
...route.value.params,
...attrs,
onVnodeUnmounted() {
// 组件卸载时清理
}
})
}
}
})嵌套路由渲染原理
<!-- 路由 /users/:id -->
<router-view> <!-- depth=0 → 取 matched[0].component -->
<UserLayout>
<router-view /> <!-- depth=1 → 取 matched[1].component -->
</UserLayout>
</router-view>通过 provide/inject 的 depth 递增机制,实现任意层级的嵌套路由渲染。每层 <router-view> 自动取 matched[depth]。
<router-link> 的实现
const RouterLink = defineComponent({
name: 'RouterLink',
props: {
to: { type: [String, Object], required: true },
replace: Boolean,
// ...
},
setup(props, { slots }) {
const router = inject(routerKey)
const route = inject(routeLocationKey)
// 计算目标路由
const href = computed(() => router.resolve(props.to).href)
const isActive = computed(() => /* 精确匹配检测 */)
const isExactActive = computed(() => router.isActive(props.to))
function navigate(event) {
if (props.replace) {
router.replace(props.to)
} else {
router.push(props.to)
}
}
return () => h('a', {
href: href.value,
onClick: navigate,
class: {
'router-link-active': isActive.value,
'router-link-exact-active': isExactActive.value
}
}, slots.default?.())
}
})阻止默认行为的特殊处理
<router-link> 在点击时会调用 event.preventDefault() 阻止浏览器默认的页面刷新导航,改用 router.push() 实现 SPA 无刷跳转。
Factory 模式:createRouter
createRouter 是工厂函数,统一创建并组合各个模块:
function createRouter(options) {
// 1. 创建路由匹配器
const matcher = createRouterMatcher(options.routes)
// 2. 创建历史记录控制器
const history = options.history // createWebHistory / createWebHashHistory
// 3. 初始化响应式 route 状态
const currentRoute = shallowRef({
path: '/',
matched: [],
params: {},
query: {},
meta: {},
hash: '',
fullPath: '/'
})
// 4. 导航守卫收集器
const guards = {
beforeEach: [],
beforeResolve: [],
afterEach: []
}
// 5. ⭐ 核心导航方法
async function push(to) {
// 解析目标路由
const targetLocation = resolve(to)
// 执行导航守卫链
const failure = await navigate(targetLocation, currentRoute.value, guards)
if (failure) {
// 导航被取消或重定向
return failure
}
// ✅ 导航确认:更新浏览器 URL + 更新响应式 route
history.push(targetLocation.fullPath)
currentRoute.value = targetLocation
}
function replace(to) {
const targetLocation = resolve(to)
history.replace(targetLocation.fullPath)
currentRoute.value = targetLocation
}
const router = {
currentRoute,
push,
replace,
resolve: (to) => matcher.resolve(to), // 委托给 matcher
addRoute: (parent, route) => matcher.addRoute(route, parent),
getRoutes: () => matcher.getRoutes(),
beforeEach: (guard) => guards.beforeEach.push(guard),
afterEach: (guard) => guards.afterEach.push(guard),
install(app) { /* 前面已介绍 */ }
}
return router
}完整导航流程源码链路
小结
| 设计模式 | Vue Router 中的应用 |
|---|---|
| Plugin | app.use(router).install() 注入全局 |
| Strategy | createWebHistory / createWebHashHistory / createMemoryHistory |
| Composite | RouteRecord 的 parent/children 树形结构 |
| Observer | reactive(currentRoute) + provide/inject 响应式通知 |
| Chain of Responsibility | 导航守卫队列,依次拦截/通过 |
| Factory | createRouter() 统一创建组合各模块 |
| Proxy | 路由变化前后,守卫链拦截导航 |
| 核心机制 | 一句话总结 |
|---|---|
| 路由匹配 | path 编译为 regex,resolve 时按深度匹配,回溯组件链 |
| 导航守卫 | 责任链模式,按序执行 5 类守卫(leave → beforeEach → beforeEnter → update → beforeResolve) |
| 响应式通知 | currentRoute 是 reactive 对象,通过 provide 注入,变化时自动通知所有 useRoute() 调用者 |
| 嵌套渲染 | 每层 <router-view> 通过 depth + provide 递增,取 matched[depth] 的 component |
| History 操作 | pushState 更改 URL 不刷新页面,popstate 监听浏览器前进后退 |
上一节:导航守卫与权限路由 下一节:Pinia 状态管理
