Skip to content

Vue Router 源代码导览

项目结构

vue-router-course/
├── 04-core-feature/          # 核心功能实现
│   ├── src/
│   │   ├── router.js        # 路由器
│   │   ├── route.js         # 路由记录
│   │   ├── history.js       # 历史管理
│   │   └── matcher.js       # 路由匹配
│   ├── test/
│   │   ├── router.test.js
│   │   ├── route.test.js
│   │   ├── history.test.js
│   │   └── matcher.test.js
│   ├── package.json
│   └── README.md
├── 05-lesson-plan.md         # 课程计划
├── 01-intro.md              # 背景研究
├── 02-arch.md              # 架构分析
└── 03-code-walkthrough.md   # 源代码导览

核心文件解析

1. router.js - 路由器

文件路径: src/router.js

核心功能:

  • 路由匹配
  • 导航管理
  • 历史管理

关键代码:

javascript
// 路由器
export class Router {
  constructor(options = {}) {
    this.routes = options.routes || []
    this.currentRoute = null
    this.history = createHistory(options.mode || 'hash')
    this.beforeEachHooks = []
    this.afterEachHooks = []
    
    this.init()
  }
  
  // 初始化路由
  init() {
    this.history.listen((location) => {
      this.handleRouteChange(location)
    })
    
    this.handleRouteChange(this.history.getCurrentLocation())
  }
  
  // 处理路由变化
  handleRouteChange(location) {
    const route = this.match(location)
    
    this.runBeforeEachHooks(route, () => {
      this.currentRoute = route
      this.runAfterEachHooks(route)
    })
  }
  
  // 匹配路由
  match(location) {
    const matcher = new Matcher(this.routes)
    return matcher.match(location)
  }
  
  // 导航到指定路由
  push(location) {
    this.history.push(location)
  }
  
  // 替换当前路由
  replace(location) {
    this.history.replace(location)
  }
  
  // 返回上一页
  go(n) {
    this.history.go(n)
  }
  
  // 添加全局前置守卫
  beforeEach(hook) {
    this.beforeEachHooks.push(hook)
  }
  
  // 添加全局后置钩子
  afterEach(hook) {
    this.afterEachHooks.push(hook)
  }
  
  // 运行全局前置守卫
  runBeforeEachHooks(route, callback) {
    let index = 0
    
    const next = () => {
      if (index >= this.beforeEachHooks.length) {
        callback()
        return
      }
      
      const hook = this.beforeEachHooks[index++]
      hook(route, this.currentRoute, next)
    }
    
    next()
  }
  
  // 运行全局后置钩子
  runAfterEachHooks(route) {
    this.afterEachHooks.forEach(hook => {
      hook(route, this.currentRoute)
    })
  }
}

设计要点:

  • 支持多种历史模式
  • 支持导航守卫
  • 支持路由匹配

2. route.js - 路由记录

文件路径: src/route.js

核心功能:

  • 存储路由配置
  • 管理子路由
  • 匹配路径

关键代码:

javascript
// 路由记录
export class RouteRecord {
  constructor(record) {
    this.path = record.path
    this.component = record.component
    this.name = record.name
    this.children = record.children || []
    this.props = record.props
    this.meta = record.meta || {}
    this.beforeEnter = record.beforeEnter
  }
  
  // 添加子路由
  addChild(record) {
    this.children.push(new RouteRecord(record))
  }
  
  // 获取所有子路由
  getChildren() {
    return this.children
  }
}

// 路由对象
export class Route {
  constructor(options = {}) {
    this.path = options.path || '/'
    this.name = options.name
    this.params = options.params || {}
    this.query = options.query || {}
    this.hash = options.hash || ''
    this.matched = options.matched || []
    this.meta = options.meta || {}
  }
  
  // 创建完整路径
  get fullPath() {
    let path = this.path
    
    if (Object.keys(this.query).length > 0) {
      const query = new URLSearchParams(this.query).toString()
      path += '?' + query
    }
    
    if (this.hash) {
      path += '#' + this.hash
    }
    
    return path
  }
}

设计要点:

  • 简单的路由配置
  • 支持嵌套路由
  • 支持自定义属性

3. history.js - 历史管理

文件路径: src/history.js

核心功能:

  • 管理 URL 历史
  • 监听 URL 变化
  • 导航控制

关键代码:

javascript
// Hash 历史管理
export class HashHistory {
  constructor() {
    window.addEventListener('hashchange', this.handleHashChange.bind(this))
  }
  
  // 获取当前位置
  getCurrentLocation() {
    return window.location.hash.slice(1) || '/'
  }
  
  // 导航到指定路径
  push(path) {
    window.location.hash = path
  }
  
  // 替换当前路径
  replace(path) {
    const url = new URL(window.location)
    url.hash = path
    window.history.replaceState(null, '', url)
  }
  
  // 前进或后退
  go(n) {
    window.history.go(n)
  }
  
  // 监听路由变化
  listen(callback) {
    this.callback = callback
  }
  
  // 处理 hash 变化
  handleHashChange() {
    if (this.callback) {
      this.callback(this.getCurrentLocation())
    }
  }
}

// HTML5 历史管理
export class HTML5History {
  constructor() {
    window.addEventListener('popstate', this.handlePopState.bind(this))
  }
  
  // 获取当前位置
  getCurrentLocation() {
    return window.location.pathname
  }
  
  // 导航到指定路径
  push(path) {
    window.history.pushState(null, '', path)
  }
  
  // 替换当前路径
  replace(path) {
    window.history.replaceState(null, '', path)
  }
  
  // 前进或后退
  go(n) {
    window.history.go(n)
  }
  
  // 监听路由变化
  listen(callback) {
    this.callback = callback
  }
  
  // 处理 popstate 事件
  handlePopState() {
    if (this.callback) {
      this.callback(this.getCurrentLocation())
    }
  }
}

// 创建历史管理器
export function createHistory(mode) {
  switch (mode) {
    case 'hash':
      return new HashHistory()
    case 'html5':
      return new HTML5History()
    default:
      return new HashHistory()
  }
}

设计要点:

  • 支持多种历史模式
  • 监听 URL 变化
  • 提供导航控制

4. matcher.js - 路由匹配

文件路径: src/matcher.js

核心功能:

  • 匹配路径
  • 解析参数
  • 构建路由对象

关键代码:

javascript
// 路由匹配器
export class Matcher {
  constructor(routes) {
    this.routes = this.createRouteRecords(routes)
  }
  
  // 创建路由记录
  createRouteRecords(routes, parent = null) {
    return routes.map(route => {
      const record = new RouteRecord({
        ...route,
        parent
      })
      
      if (route.children) {
        record.children = this.createRouteRecords(route.children, record)
      }
      
      return record
    })
  }
  
  // 匹配路径
  match(path) {
    const matched = this.matchRoute(path, this.routes)
    
    if (!matched) {
      return null
    }
    
    return new Route({
      path: matched.path,
      params: matched.params,
      matched: matched.records
    })
  }
  
  // 匹配路由
  matchRoute(path, routes) {
    const segments = path.split('/').filter(Boolean)
    let currentRoutes = routes
    const matchedRecords = []
    let matchedParams = {}
    let matchedPath = ''
    
    for (const segment of segments) {
      let matched = null
      
      for (const route of currentRoutes) {
        const result = this.matchPathSegment(segment, route.path)
        
        if (result) {
          matched = { route, params: result.params }
          break
        }
      }
      
      if (!matched) {
        return null
      }
      
      matchedRecords.push(matched.route)
      Object.assign(matchedParams, matched.params)
      matchedPath += '/' + segment
      currentRoutes = matched.route.children || []
    }
    
    return {
      path: matchedPath || '/',
      params: matchedParams,
      records: matchedRecords
    }
  }
  
  // 匹配路径段
  matchPathSegment(segment, routePath) {
    // 精确匹配
    if (segment === routePath) {
      return { params: {} }
    }
    
    // 动态参数匹配
    const paramMatch = routePath.match(/^:([^/]+)$/)
    if (paramMatch) {
      return { params: { [paramMatch[1]]: segment } }
    }
    
    return null
  }
}

设计要点:

  • 支持动态参数
  • 支持嵌套路由
  • 精确的路径匹配

关键设计决策

1. 分离历史管理

原因:

  • 支持多种历史模式
  • 易于扩展
  • 职责分离

实现:

javascript
class HashHistory { }
class HTML5History { }

function createHistory(mode) {
  switch (mode) {
    case 'hash': return new HashHistory()
    case 'html5': return new HTML5History()
  }
}

2. 使用守卫系统

原因:

  • 控制导航流程
  • 支持权限验证
  • 支持数据预取

实现:

javascript
router.beforeEach((to, from, next) => {
  // 守卫逻辑
  next()
})

3. 嵌套路由

原因:

  • 支持复杂布局
  • 支持组件嵌套
  • 支持参数传递

实现:

javascript
{
  path: '/user',
  component: User,
  children: [
    {
      path: 'profile',
      component: Profile
    }
  ]
}

测试策略

单元测试

javascript
import { describe, it } from 'node:test'
import assert from 'node:assert'
import { Router } from '../src/router.js'

describe('Router 测试', () => {
  it('应该创建路由器', () => {
    const router = new Router({
      routes: [
        { path: '/', component: Home }
      ]
    })
    assert.ok(router)
  })
  
  it('应该匹配路由', () => {
    const router = new Router({
      routes: [
        { path: '/', component: Home }
      ]
    })
    const route = router.match('/')
    assert.ok(route)
  })
})

总结

Vue Router 的源代码体现了以下设计原则:

  1. 模块化:清晰的模块划分
  2. 灵活性:支持多种路由模式
  3. 可扩展:支持自定义路由和守卫
  4. 易于测试:提供清晰的接口

理解源代码有助于更好地使用和优化 Vue Router。

参考资源

架构师AI杜公众号二维码

扫描二维码关注"架构师AI杜"公众号,获取更多技术内容和最新动态