Vue Router 是 Vue.js 官方的路由管理器,它的核心实现涉及多个关键机制。下面我将从核心概念、路由模式、实现细节和关键源码等方面深入解析 Vue Router 的工作原理。
一、核心架构
1. 整体架构图
┌───────────────────────────────────────────────────┐
│ Vue Components │
└───────────────┬───────────────────┬───────────────┘
│ │
↓ ↓
┌───────────────┴───┐ ┌────────┴─────────────┐
│ Router-Link │ │ Router-View │
└───────────────────┘ └──────────────────────┘
│ │
└─────────┬─────────┘
↓
┌───────────────────────────────────────────────────┐
│ Vue Router │
│ │
│ ┌─────────────┐ ┌────────────┐ ┌───────┐ │
│ │ History │ │ Route │ │ URL │ │
│ │ Manager │ │ Matcher │ │ │ │
│ └─────────────┘ └────────────┘ └───────┘ │
└───────────────────────────────────────────────────┘
二、路由模式实现原理
1. Hash 模式
实现原理:
- 基于 URL 的 hash 部分(
#
后面的内容) - 监听
hashchange
事件
特点:
- 兼容性好(支持 IE8)
- 不需要服务器端配置
- URL 中带有
#
不太美观
关键实现:
class HashHistory {
constructor(router) {
window.addEventListener('hashchange', () => {
this.transitionTo(getHash())
})
}
push(location) {
window.location.hash = location
}
replace(location) {
window.location.replace(getUrl(location))
}
}
2. History 模式
实现原理:
- 基于 HTML5 History API(pushState/replaceState)
- 监听
popstate
事件
特点:
- URL 更美观(无
#
) - 需要服务器端支持(避免 404)
- 兼容性要求较高(IE10+)
关键实现:
class HTML5History {
constructor(router) {
window.addEventListener('popstate', (e) => {
this.transitionTo(getLocation())
})
}
push(location) {
history.pushState({}, '', location)
this.transitionTo(location)
}
replace(location) {
history.replaceState({}, '', location)
this.transitionTo(location)
}
}
3. Abstract 模式
实现场景:
- 非浏览器环境(如 Node.js、Weex)
- 内存中的路由管理
特点:
- 不依赖浏览器 API
- 适用于服务端渲染或原生应用
三、核心实现机制
1. 路由匹配器(Route Matcher)
功能:
- 解析路由配置
- 创建路由映射表
- 匹配当前 URL 对应的路由
实现原理:
function createRouteMap(routes) {
const pathList = []
const pathMap = Object.create(null)
const nameMap = Object.create(null)
routes.forEach(route => {
addRouteRecord(pathList, pathMap, nameMap, route)
})
return {
pathList,
pathMap,
nameMap
}
}
function addRouteRecord(pathList, pathMap, nameMap, route, parent) {
const path = parent ? `${parent.path}/${route.path}` : route.path
const record = {
path,
component: route.component,
name: route.name,
parent,
// ...其他属性
}
if (!pathMap[path]) {
pathList.push(path)
pathMap[path] = record
}
if (route.name && !nameMap[route.name]) {
nameMap[route.name] = record
}
// 递归处理子路由
if (route.children) {
route.children.forEach(child => {
addRouteRecord(pathList, pathMap, nameMap, child, record)
})
}
}
2. 路由跳转流程
-
导航触发:
- 调用
router.push
或router.replace
- 点击
<router-link>
- 浏览器前进/后退
- 调用
-
导航守卫解析:
- 调用离开守卫(beforeRouteLeave)
- 调用全局 beforeEach 守卫
- 在重用组件中调用 beforeRouteUpdate
- 调用路由配置中的 beforeEnter
- 解析异步路由组件
- 在被激活的组件中调用 beforeRouteEnter
- 调用全局 beforeResolve 守卫
-
确认导航:
- 调用全局 afterEach 钩子
-
更新视图:
- 触发 DOM 更新
3. 路由视图渲染(Router-View)
实现原理:
- 函数式组件
- 根据当前路由匹配的组件层级渲染对应组件
简化实现:
const RouterView = {
functional: true,
render(_, { props, children, parent, data }) {
const route = parent.$route
const matched = route.matched[depth]
const component = matched && matched.components[name]
return h(component, data, children)
}
}
4. 路由链接(Router-Link)
实现原理:
- 生成正确的
<a>
标签 - 处理点击事件(阻止默认行为,调用 router.push/replace)
- 激活样式处理
简化实现:
const RouterLink = {
props: {
to: { type: [String, Object], required: true },
tag: { type: String, default: 'a' },
replace: Boolean,
activeClass: String,
exactActiveClass: String
},
render(h) {
const router = this.$router
const current = this.$route
const { location, route, href } = router.resolve(this.to)
const classes = {}
const activeClass = this.activeClass || router.options.linkActiveClass
const exactActiveClass = this.exactActiveClass || router.options.linkExactActiveClass
classes[activeClass] = this.isActive(route, current)
classes[exactActiveClass] = this.isExactActive(route, current)
const handler = e => {
if (this.replace) {
router.replace(location)
} else {
router.push(location)
}
}
return h(this.tag, {
class: classes,
attrs: { href },
on: { click: handler }
}, this.$slots.default)
}
}
四、导航守卫实现
1. 守卫类型
-
全局守卫:
- beforeEach
- beforeResolve
- afterEach
-
路由独享守卫:
- beforeEnter
-
组件内守卫:
- beforeRouteEnter
- beforeRouteUpdate
- beforeRouteLeave
2. 守卫执行流程实现
function runQueue(queue, fn, cb) {
const step = index => {
if (index >= queue.length) {
cb()
} else {
if (queue[index]) {
fn(queue[index], () => {
step(index + 1)
})
} else {
step(index + 1)
}
}
}
step(0)
}
function confirmTransition(route, onComplete, onAbort) {
const current = this.current
const abort = err => {
// 处理中止逻辑
}
const lastRouteIndex = route.matched.length - 1
const lastCurrentIndex = current.matched.length - 1
// 判断是否是相同路由
if (isSameRoute(route, current)) {
this.ensureURL()
return abort()
}
// 解析导航守卫队列
const queue = [
// 离开守卫
...extractLeaveGuards(deactivated),
// 全局 beforeEach
this.router.beforeHooks,
// 组件 beforeRouteUpdate
...extractUpdateHooks(updated),
// 路由配置 beforeEnter
...extractEnterGuards(activated),
// 解析异步组件
resolveAsyncComponents(activated)
]
// 执行队列
runQueue(queue, iterator, () => {
// 等待 beforeRouteEnter
const enterGuards = extractEnterGuards(activated, postEnterCbs, () => {
return this.current === route
})
runQueue(enterGuards, iterator, () => {
// 完成导航
onComplete(route)
// 触发 afterEach
this.router.app.$nextTick(() => {
postEnterCbs.forEach(cb => {
cb()
})
})
})
})
}
五、动态路由实现
1. 添加路由
function addRoute(parentOrRoute, route) {
const parent = typeof parentOrRoute === 'object' ? null : parentOrRoute
const routeRecord = typeof parentOrRoute === 'object' ? parentOrRoute : route
// 创建路由记录
const pathMap = this.matcher.pathMap
const nameMap = this.matcher.nameMap
const pathList = this.matcher.pathList
addRouteRecord(pathList, pathMap, nameMap, routeRecord, parent)
// 如果当前路径匹配新路由,触发导航
if (this.current.path !== '/') {
this.transitionTo(this.current)
}
}
2. 路由懒加载
实现原理:
- 使用 Webpack 的动态 import 语法
- 返回 Promise 的组件定义
示例:
const Foo = () => import('./Foo.vue')
const router = new VueRouter({
routes: [
{ path: '/foo', component: Foo }
]
})
内部处理:
function resolveAsyncComponents(matched) {
return (to, from, next) => {
let hasAsync = false
let pending = 0
let error = null
matched.forEach(match => {
if (typeof match.components.default === 'function') {
hasAsync = true
pending++
const resolve = once(resolvedComponent => {
match.components.default = resolvedComponent
pending--
if (pending <= 0) {
next()
}
})
const reject = once(reason => {
error = reason
pending--
if (pending <= 0) {
next(error)
}
})
try {
match.components.default(resolve, reject)
} catch (e) {
reject(e)
}
}
})
if (!hasAsync) next()
}
}
六、路由过渡动画
实现原理:
- 利用 Vue 的
<transition>
组件 - 基于路由变化的元信息控制动画
示例:
<transition :name="transitionName">
<router-view></router-view>
</transition>
watch: {
'$route' (to, from) {
const toDepth = to.path.split('/').length
const fromDepth = from.path.split('/').length
this.transitionName = toDepth < fromDepth ? 'slide-right' : 'slide-left'
}
}
七、关键源码解析
1. VueRouter 类核心结构
class VueRouter {
constructor(options = {}) {
this.app = null
this.apps = []
this.options = options
this.matcher = createMatcher(options.routes || [], this)
this.mode = options.mode || 'hash'
switch (this.mode) {
case 'history':
this.history = new HTML5History(this, options.base)
break
case 'hash':
this.history = new HashHistory(this, options.base, this.fallback)
break
case 'abstract':
this.history = new AbstractHistory(this, options.base)
break
default:
throw new Error(`invalid mode: ${this.mode}`)
}
}
init(app) {
this.apps.push(app)
// 设置路由监听
this.history.transitionTo(
this.history.getCurrentLocation(),
() => {
this.history.setupListeners()
}
)
}
push(location) {
this.history.push(location)
}
replace(location) {
this.history.replace(location)
}
// ...其他方法
}
2. 路由匹配核心逻辑
function match(
raw,
currentRoute,
redirectedFrom
) {
const location = normalizeLocation(raw, currentRoute, false, router)
const { name } = location
if (name) {
const record = nameMap[name]
if (!record) return _createRoute(null, location)
const paramNames = record.regex.keys
.filter(key => !key.optional)
.map(key => key.name)
if (typeof location.params !== 'object') {
location.params = {}
}
if (currentRoute && typeof currentRoute.params === 'object') {
for (const key in currentRoute.params) {
if (!(key in location.params) && paramNames.indexOf(key) > -1) {
location.params[key] = currentRoute.params[key]
}
}
}
location.path = fillParams(record.path, location.params)
return _createRoute(record, location, redirectedFrom)
} else if (location.path) {
location.params = {}
for (let i = 0; i < pathList.length; i++) {
const path = pathList[i]
const record = pathMap[path]
if (matchRoute(record.regex, location.path, location.params)) {
return _createRoute(record, location, redirectedFrom)
}
}
}
return _createRoute(null, location)
}
八、总结:Vue Router 设计精妙之处
-
多种路由模式支持:
- 通过策略模式实现不同路由管理方式
- 统一对外 API,内部实现差异透明
-
嵌套路由系统:
- 递归匹配路由记录
- 组件层级与路由层级对应
-
导航守卫机制:
- 完整的导航解析流程
- 异步守卫支持
- 灵活的导航控制能力
-
组件化集成:
<router-view>
作为函数式组件<router-link>
封装导航逻辑
-
动态路由支持:
- 运行时添加路由
- 懒加载与代码分割
理解 Vue Router 的实现原理有助于:
- 更高效地使用路由功能
- 能够处理复杂路由场景
- 在遇到路由问题时能够快速定位
- 为自定义路由需求提供思路