【DeepSeek帮我准备前端面试100问】(十四)Vue Router详解

2 阅读4分钟

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. 路由跳转流程

  1. 导航触发

    • 调用 router.pushrouter.replace
    • 点击 <router-link>
    • 浏览器前进/后退
  2. 导航守卫解析

    • 调用离开守卫(beforeRouteLeave)
    • 调用全局 beforeEach 守卫
    • 在重用组件中调用 beforeRouteUpdate
    • 调用路由配置中的 beforeEnter
    • 解析异步路由组件
    • 在被激活的组件中调用 beforeRouteEnter
    • 调用全局 beforeResolve 守卫
  3. 确认导航

    • 调用全局 afterEach 钩子
  4. 更新视图

    • 触发 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. 守卫类型

  1. 全局守卫

    • beforeEach
    • beforeResolve
    • afterEach
  2. 路由独享守卫

    • beforeEnter
  3. 组件内守卫

    • 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 设计精妙之处

  1. 多种路由模式支持

    • 通过策略模式实现不同路由管理方式
    • 统一对外 API,内部实现差异透明
  2. 嵌套路由系统

    • 递归匹配路由记录
    • 组件层级与路由层级对应
  3. 导航守卫机制

    • 完整的导航解析流程
    • 异步守卫支持
    • 灵活的导航控制能力
  4. 组件化集成

    • <router-view> 作为函数式组件
    • <router-link> 封装导航逻辑
  5. 动态路由支持

    • 运行时添加路由
    • 懒加载与代码分割

理解 Vue Router 的实现原理有助于:

  • 更高效地使用路由功能
  • 能够处理复杂路由场景
  • 在遇到路由问题时能够快速定位
  • 为自定义路由需求提供思路