Skip to content

Vue Router 课程计划

课程概述

本课程将带你从零开始实现一个路由系统,理解 Vue Router 的核心原理。

课程目标

  • 理解路由系统的原理
  • 掌握路由匹配和导航
  • 学习路由守卫和历史管理
  • 能够构建自己的路由库

课程结构

8 节课,每节 20-40 分钟,总时长约 90-120 分钟。


第 1 课:Vue Router 简介

目标

  • 了解 Vue Router 的背景
  • 理解路由系统的概念
  • 掌握 Vue Router 的核心功能

内容

  1. Vue Router 背景

    • 什么是路由
    • 为什么需要路由
    • SPA 的路由
  2. 核心概念

    • 路由器
    • 路由
    • 导航守卫
    • 路由参数
  3. 路由模式

    • Hash 模式
    • History 模式
    • Memory 模式

实践步骤

bash
# 1. 创建项目
mkdir vue-router-course
cd vue-router-course

# 2. 初始化项目
npm init -y

# 3. 安装 Vue
npm install vue --save

预期输出

✓ 项目初始化完成
✓ Vue 安装完成

总结

  • ✅ 理解了路由的概念
  • ✅ 掌握了核心功能
  • ✅ 了解了路由模式

第 2 课:路由匹配

目标

  • 理解路由匹配的原理
  • 实现路由匹配器
  • 学习参数提取

内容

  1. 路由匹配

    • 什么是路由匹配
    • 匹配算法
    • 参数提取
  2. 实现路由匹配

    • 路径匹配
    • 正则表达式
    • 参数解析
  3. 测试路由匹配

    • 单元测试
    • 边界测试

实践步骤

bash
# 1. 创建路由匹配器
cat > src/matcher.js << 'EOF'
export function matchRoute(path, route) {
  const regex = new RegExp('^' + route.path.replace(/:\w+/g, '([^/]+)') + '$')
  const match = path.match(regex)
  
  if (match) {
    return {
      route,
      params: extractParams(match, route.path)
    }
  }
  
  return null
}

function extractParams(match, path) {
  const params = {}
  const paramNames = path.match(/:(\w+)/g) || []
  
  paramNames.forEach((name, index) => {
    params[name.substring(1)] = match[index + 1]
  })
  
  return params
}
EOF

# 2. 创建测试
cat > test/matcher.test.js << 'EOF'
import { describe, it } from 'node:test'
import assert from 'node:assert'
import { matchRoute } from '../src/matcher.js'

describe('路由匹配测试', () => {
  it('应该匹配路由', () => {
    const route = { path: '/users/:id' }
    const result = matchRoute('/users/123', route)
    assert.ok(result)
    assert.strictEqual(result.params.id, '123')
  })
})
EOF

# 3. 运行测试
npm test

预期输出

✓ 应该匹配路由

总结

  • ✅ 实现了路由匹配
  • ✅ 理解了匹配算法
  • ✅ 掌握了参数提取

第 3 课:路由器实现

目标

  • 理解路由器的原理
  • 实现路由器
  • 学习路由注册

内容

  1. 路由器

    • 什么是路由器
    • 路由器职责
    • 路由器配置
  2. 实现路由器

    • 路由注册
    • 路由匹配
    • 当前路由
  3. 测试路由器

    • 单元测试
    • 集成测试

实践步骤

bash
# 1. 创建路由器
cat > src/router.js << 'EOF'
export class Router {
  constructor(options = {}) {
    this.routes = options.routes || []
    this.currentRoute = null
  }
  
  addRoute(route) {
    this.routes.push(route)
  }
  
  match(path) {
    for (const route of this.routes) {
      const result = matchRoute(path, route)
      if (result) {
        this.currentRoute = result
        return result
      }
    }
    return null
  }
}
EOF

# 2. 创建测试
cat > test/router.test.js << 'EOF'
import { describe, it } from 'node:test'
import assert from 'node:assert'
import { Router } from '../src/router.js'

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

# 3. 运行测试
npm test

预期输出

✓ 应该创建路由器
✓ 应该匹配路由

总结

  • ✅ 实现了路由器
  • ✅ 理解了路由器职责
  • ✅ 掌握了路由注册

第 4 课:历史管理

目标

  • 理解历史管理的原理
  • 实现历史管理器
  • 学习 URL 同步

内容

  1. 历史管理

    • 什么是历史管理
    • History API
    • Hash 模式
  2. 实现历史管理

    • URL 同步
    • 导航控制
    • 事件监听
  3. 测试历史管理

    • 单元测试
    • 浏览器测试

实践步骤

bash
# 1. 创建历史管理器
cat > src/history.js << 'EOF'
export class HTML5History {
  constructor(router) {
    this.router = router
    this.current = window.location.pathname
    
    window.addEventListener('popstate', this.handlePopState.bind(this))
  }
  
  push(path) {
    window.history.pushState({}, '', path)
    this.current = path
    this.router.match(path)
  }
  
  replace(path) {
    window.history.replaceState({}, '', path)
    this.current = path
    this.router.match(path)
  }
  
  handlePopState(event) {
    this.current = window.location.pathname
    this.router.match(this.current)
  }
}
EOF

# 2. 创建测试
cat > test/history.test.js << 'EOF'
import { describe, it } from 'node:test'
import assert from 'node:assert'
import { HTML5History } from '../src/history.js'

describe('历史管理测试', () => {
  it('应该创建历史管理器', () => {
    const history = new HTML5History({})
    assert.ok(history)
  })
})
EOF

# 3. 运行测试
npm test

预期输出

✓ 应该创建历史管理器

总结

  • ✅ 实现了历史管理
  • ✅ 理解了 History API
  • ✅ 掌握了 URL 同步

第 5 课:路由守卫

目标

  • 理解路由守卫的原理
  • 实现路由守卫
  • 学习导航控制

内容

  1. 路由守卫

    • 什么是路由守卫
    • 守卫类型
    • 执行顺序
  2. 实现路由守卫

    • 全局守卫
    • 路由独享守卫
    • 组件内守卫
  3. 测试路由守卫

    • 单元测试
    • 集成测试

实践步骤

bash
# 1. 创建路由守卫
cat > src/guards.js << 'EOF'
export class RouterGuard {
  constructor(router) {
    this.router = router
    this.beforeEachGuards = []
    this.afterEachGuards = []
  }
  
  beforeEach(guard) {
    this.beforeEachGuards.push(guard)
  }
  
  afterEach(guard) {
    this.afterEachGuards.push(guard)
  }
  
  async runBeforeGuards(to, from) {
    for (const guard of this.beforeEachGuards) {
      const result = await guard(to, from)
      if (result !== undefined) {
        return result
      }
    }
  }
  
  async runAfterGuards(to, from) {
    for (const guard of this.afterEachGuards) {
      await guard(to, from)
    }
  }
}
EOF

# 2. 创建测试
cat > test/guards.test.js << 'EOF'
import { describe, it } from 'node:test'
import assert from 'node:assert'
import { RouterGuard } from '../src/guards.js'

describe('路由守卫测试', () => {
  it('应该创建路由守卫', () => {
    const guard = new RouterGuard({})
    assert.ok(guard)
  })
  
  it('应该执行守卫', async () => {
    const guard = new RouterGuard({})
    let executed = false
    
    guard.beforeEach(() => {
      executed = true
    })
    
    await guard.runBeforeGuards({}, {})
    assert.strictEqual(executed, true)
  })
})
EOF

# 3. 运行测试
npm test

预期输出

✓ 应该创建路由守卫
✓ 应该执行守卫

总结

  • ✅ 实现了路由守卫
  • ✅ 理解了守卫类型
  • ✅ 掌握了导航控制

第 6 课:嵌套路由

目标

  • 理解嵌套路由的原理
  • 实现嵌套路由
  • 学习路由视图

内容

  1. 嵌套路由

    • 什么是嵌套路由
    • 路由视图
    • 嵌套配置
  2. 实现嵌套路由

    • 路由嵌套
    • 视图渲染
    • 参数传递
  3. 测试嵌套路由

    • 单元测试
    • 集成测试

实践步骤

bash
# 1. 创建嵌套路由
cat > src/nested.js << 'EOF'
export function resolveNestedRoute(routes, path) {
  const segments = path.split('/').filter(Boolean)
  let currentRoutes = routes
  let matched = []
  
  for (const segment of segments) {
    const matchedRoute = currentRoutes.find(route => {
      return route.path === '/' + segment || route.path === '/' + segment + '/:id'
    })
    
    if (matchedRoute) {
      matched.push(matchedRoute)
      currentRoutes = matchedRoute.children || []
    }
  }
  
  return matched
}
EOF

# 2. 创建测试
cat > test/nested.test.js << 'EOF'
import { describe, it } from 'node:test'
import assert from 'node:assert'
import { resolveNestedRoute } from '../src/nested.js'

describe('嵌套路由测试', () => {
  it('应该解析嵌套路由', () => {
    const routes = [
      {
        path: '/users',
        children: [
          { path: '/:id', component: 'UserDetail' }
        ]
      }
    ]
    
    const matched = resolveNestedRoute(routes, '/users/123')
    assert.ok(matched.length > 0)
  })
})
EOF

# 3. 运行测试
npm test

预期输出

✓ 应该解析嵌套路由

总结

  • ✅ 实现了嵌套路由
  • ✅ 理解了路由视图
  • ✅ 掌握了嵌套配置

第 7 课:动态路由

目标

  • 理解动态路由的原理
  • 实现动态路由
  • 学习路由懒加载

内容

  1. 动态路由

    • 什么是动态路由
    • 动态添加路由
    • 路由懒加载
  2. 实现动态路由

    • 路由注册
    • 组件加载
    • 路由移除
  3. 测试动态路由

    • 单元测试
    • 集成测试

实践步骤

bash
# 1. 创建动态路由
cat > src/dynamic.js << 'EOF'
export function addDynamicRoute(router, route) {
  router.addRoute(route)
}

export function removeDynamicRoute(router, path) {
  const index = router.routes.findIndex(r => r.path === path)
  if (index > -1) {
    router.routes.splice(index, 1)
  }
}

export function loadComponent(component) {
  if (typeof component === 'function') {
    return component()
  }
  return Promise.resolve(component)
}
EOF

# 2. 创建测试
cat > test/dynamic.test.js << 'EOF'
import { describe, it } from 'node:test'
import assert from 'node:assert'
import { addDynamicRoute, removeDynamicRoute } from '../src/dynamic.js'

describe('动态路由测试', () => {
  it('应该添加动态路由', () => {
    const router = { routes: [] }
    addDynamicRoute(router, { path: '/dynamic' })
    assert.strictEqual(router.routes.length, 1)
  })
  
  it('应该移除动态路由', () => {
    const router = { routes: [{ path: '/dynamic' }] }
    removeDynamicRoute(router, '/dynamic')
    assert.strictEqual(router.routes.length, 0)
  })
})
EOF

# 3. 运行测试
npm test

预期输出

✓ 应该添加动态路由
✓ 应该移除动态路由

总结

  • ✅ 实现了动态路由
  • ✅ 理解了路由懒加载
  • ✅ 掌握了组件加载

第 8 课:总结与扩展

目标

  • 总结所有课程
  • 回顾关键概念
  • 提供扩展建议

内容

  1. 课程总结

    • 回顾所有功能
    • 总结关键概念
    • 展示完整代码
  2. 扩展建议

    • 添加更多功能
    • 优化性能
    • 支持更多特性
  3. 下一步

    • 学习 Vue Router 源码
    • 参与开源项目
    • 构建自己的路由库

总结

通过本课程,你学会了:

  1. ✅ Vue Router 简介
  2. ✅ 路由匹配
  3. ✅ 路由器实现
  4. ✅ 历史管理
  5. ✅ 路由守卫
  6. ✅ 嵌套路由
  7. ✅ 动态路由

下一步

  • 学习 Vue Router 源码
  • 添加更多功能
  • 参与开源项目

参考资源

架构师AI杜公众号二维码

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