vueRouter

Vue Router

  1. 前端路由的本质【原理】
    传统多页应用时代,路由属于后端,服务器根据不同的URL返回不同的HTML页面。每次页面切换都需要重新请求服务器,导致白屏问题。

    单页应用(SPA)时代,只有一个HTML页面,视图切换变成了页面上某个模块的切换(即页面级组件的切换)。前端路由维护的是URL与页面级组件的映射关系,当URL变化时,动态渲染对应的组件,而不重新请求页面。

  2. 路由的两种实现模式【原理】
    Vue Router支持两种浏览器环境下的模式:

    Hash模式

    • 利用URL中的#符号(hash)
    • 特点:hash变化不会发送HTTP请求,完全由前端处理;兼容性好
    • 原理:通过window.location.hash赋值改变路由,监听hashchange事件响应变化
    • 适用:无需服务器配置,适合简单应用或静态托管

    History模式

    • 利用HTML5的History API(pushStatereplaceState
    • 特点:URL美观,没有#;需要服务器配合,否则刷新会404
    • 原理:通过history.pushState修改URL而不刷新页面,监听popstate事件响应变化
    • 适用:生产环境,需要服务器配置回退到index.html
    const router = createRouter({
      history: createWebHistory(), // History模式
      // history: createWebHashHistory() // Hash模式
      routes
    })
    
  3. 核心原理:路由与视图的联动【原理】
    Vue Router的核心工作机制可以概括为三部分:

    ① 路由注册(install)

    • 通过Vue.use(Router)调用插件的install方法
    • 全局混入beforeCreate钩子,为所有组件注入_router_route属性
    • 通过Vue.util.defineReactive_route变为响应式数据
    • 全局注册RouterViewRouterLink组件

    ② 路由匹配(matcher)

    • 将开发者配置的routes数组(树形结构)转换为扁平的路由记录表pathMapnameMap
    • 每条路由记录包含:pathregexcomponentsparentmeta等信息
    • 路由匹配时,通过pathname快速查找到对应的路由记录,生成$route对象

    ③ 视图更新

    • 当路由变化时,调用History.transitionTo进行导航转换
    • 更新history.current(当前路由对象)
    • 触发根组件_route的响应式更新
    • RouterView组件依赖_route数据,重新渲染匹配的组件

    伪代码示例:

    // 简化版RouterView实现
    const RouterView = {
      setup() {
        const route = inject('route')
        const depth = inject('depth', 0)
        const matched = computed(() => route.matched[depth])
        return () => {
          const component = matched.value?.components.default
          return component ? h(component) : null
        }
      }
    }
    
  4. 基础使用【入门】

    // router/index.js
    import { createRouter, createWebHistory } from 'vue-router'
    import Home from '@/views/Home.vue'
    
    const routes = [
      {
        path: '/',
        name: 'Home',
        component: Home
      },
      {
        path: '/about',
        name: 'About',
        component: () => import('@/views/About.vue') // 路由懒加载
      }
    ]
    
    const router = createRouter({
      history: createWebHistory(),
      routes
    })
    
    export default router
    
    // main.js
    import { createApp } from 'vue'
    import App from './App.vue'
    import router from './router'
    
    createApp(App).use(router).mount('#app')
    
    <!-- App.vue -->
    <template>
      <nav>
        <router-link to="/">首页</router-link>
        <router-link to="/about">关于</router-link>
      </nav>
      <router-view /> <!-- 路由组件渲染位置 -->
    </template>
    
  5. 动态路由匹配【进阶】

    const routes = [
      {
        path: '/user/:id', // 动态路径参数
        component: User,
        props: true // 将params作为props传给组件
      }
    ]
    
    <!-- User.vue -->
    <template>
      <div>用户ID:{{ id }}</div>
    </template>
    
    <script setup>
    import { useRoute } from 'vue-router'
    const route = useRoute()
    console.log(route.params.id) // 访问动态参数
    
    // 或者通过props接收
    defineProps(['id'])
    </script>
    
  6. 嵌套路由【进阶】

    const routes = [
      {
        path: '/user/:id',
        component: User,
        children: [
          { path: 'profile', component: UserProfile }, // /user/123/profile
          { path: 'posts', component: UserPosts }      // /user/123/posts
        ]
      }
    ]
    
    <!-- User.vue -->
    <template>
      <div>
        <h1>用户信息</h1>
        <router-view /> <!-- 子路由出口 -->
      </div>
    </template>
    
  7. 导航守卫【进阶】
    导航守卫用于在路由跳转前后执行逻辑,如权限验证、数据预取等。

    ① 全局守卫

    // 全局前置守卫
    router.beforeEach((to, from) => {
      // 返回false取消导航,返回路径重定向,返回true或undefined放行
      if (to.meta.requiresAuth && !isLoggedIn()) {
        return '/login'
      }
    })
    
    // 全局解析守卫(组件内守卫和异步组件解析后触发)
    router.beforeResolve((to) => {
      if (to.meta.requiresCamera) {
        return checkCameraPermission()
      }
    })
    
    // 全局后置钩子(不能改变导航)
    router.afterEach((to, from, failure) => {
      if (!failure) sendAnalytics(to.fullPath)
    })
    

    ② 路由独享守卫

    const routes = [
      {
        path: '/admin',
        component: Admin,
        beforeEnter: (to, from) => {
          if (!isAdmin()) return '/unauthorized'
        }
      }
    ]
    

    ③ 组件内守卫

    <script>
    export default {
      beforeRouteEnter(to, from, next) {
        // 进入组件前,不能访问this
        next(vm => { /* 可以访问组件实例 */ })
      },
      beforeRouteUpdate(to, from) {
        // 同一组件不同参数时调用(如/user/1 → /user/2)
        this.loadData(to.params.id)
      },
      beforeRouteLeave(to, from) {
        // 离开组件前
        const answer = confirm('确定离开吗?')
        if (!answer) return false
      }
    }
    </script>
    
  8. 路由元信息【最佳实践】
    通过meta字段附加自定义数据,如权限、标题等。

    const routes = [
      {
        path: '/admin',
        component: Admin,
        meta: { 
          requiresAuth: true,
          roles: ['admin'],
          title: '管理后台'
        }
      }
    ]
    
    // 在导航守卫中访问
    router.beforeEach((to) => {
      if (to.meta.requiresAuth && !hasRole(to.meta.roles)) {
        return '/unauthorized'
      }
    })
    
    // 在后置钩子中设置标题
    router.afterEach((to) => {
      document.title = to.meta.title || '默认标题'
    })
    
  9. 路由懒加载【优化】
    使用动态导入,按需加载组件,减少首屏加载时间。

    const routes = [
      {
        path: '/heavy',
        component: () => import(/* webpackChunkName: "heavy" */ '@/views/HeavyComponent.vue')
      }
    ]
    
  10. 编程式导航【技巧】

    import { useRouter } from 'vue-router'
    const router = useRouter()
    
    // 字符串路径
    router.push('/user/123')
    
    // 对象形式
    router.push({ path: '/user/123' })
    router.push({ name: 'User', params: { id: 123 } })
    router.push({ path: '/search', query: { q: 'vue' } })
    
    // 替换当前历史记录
    router.replace('/login')
    
    // 前进/后退
    router.go(1)  // 前进
    router.back() // 后退
    
  11. 完整导航解析流程【原理】
    从触发导航到视图更新的完整流程:

    1. 导航被触发
    2. 失活的组件里调用beforeRouteLeave守卫
    3. 调用全局beforeEach守卫
    4. 复用组件里调用beforeRouteUpdate守卫
    5. 路由配置里调用beforeEnter守卫
    6. 解析异步路由组件
    7. 激活组件里调用beforeRouteEnter守卫
    8. 调用全局beforeResolve守卫
    9. 导航确认
    10. 调用全局afterEach钩子
    11. 触发DOM更新
    12. 调用beforeRouteEnter中传给next的回调函数
  12. 权限控制实战【最佳实践】
    权限控制的核心需求:不同角色的用户只能访问其权限范围内的页面,未登录用户跳转到登录页,无权限用户跳转到403。

    ① 基本原理

    • 前端维护一份公共路由(如登录页、注册页、404页)
    • 登录成功后,后端返回当前用户的权限标识(如角色列表、可访问的路由名称或路径)
    • 前端根据权限动态生成该用户特有的路由表,通过router.addRoute动态添加
    • 在全局前置守卫中校验访问权限

    ② 为什么需要动态路由?
    静态路由配置所有可能的路由,但未授权用户虽然看不到入口,却可能通过URL直接访问。动态路由确保未授权的路由根本就不存在,从根源上防止越权。

    ③ 代码实现示例

    // 公共路由(所有人可访问)
    const constantRoutes = [
      { path: '/login', component: Login },
      { path: '/404', component: NotFound },
      { path: '/', redirect: '/dashboard' }
    ]
    
    // 异步路由(需要权限的页面)
    const asyncRoutes = [
      {
        path: '/admin',
        component: Layout,
        meta: { roles: ['admin'] },
        children: [
          { path: 'users', component: UserManage, meta: { roles: ['admin'] } },
          { path: 'settings', component: SystemSetting, meta: { roles: ['admin'] } }
        ]
      },
      {
        path: '/editor',
        component: Layout,
        meta: { roles: ['editor', 'admin'] },
        children: [
          { path: 'articles', component: ArticleList, meta: { roles: ['editor', 'admin'] } }
        ]
      }
    ]
    
    // 路由守卫
    router.beforeEach(async (to, from) => {
      const token = localStorage.getItem('token')
      if (to.path === '/login') {
        if (token) return '/' // 已登录,跳转首页
        return true
      }
      
      if (!token) return '/login'
      
      // 已登录,尝试获取用户权限
      const userStore = useUserStore()
      if (!userStore.roles.length) {
        try {
          // 请求后端获取用户角色和权限路由
          const { roles, dynamicRoutes } = await fetchUserInfo()
          userStore.setRoles(roles)
          
          // 根据角色过滤出可访问的路由表
          const accessibleRoutes = filterAsyncRoutes(asyncRoutes, roles)
          // 动态添加路由
          accessibleRoutes.forEach(route => router.addRoute(route))
          // 添加404通配路由(必须最后添加)
          router.addRoute({ path: '/:pathMatch(.*)*', redirect: '/404' })
          
          // 重新触发当前导航
          return { ...to, replace: true }
        } catch (error) {
          // token无效,跳转登录
          return '/login'
        }
      }
      
      // 检查当前路由是否有权限访问
      if (to.meta.roles && !to.meta.roles.some(r => userStore.roles.includes(r))) {
        return '/403' // 无权限跳转403页面
      }
      
      return true
    })
    
    // 辅助函数:根据角色过滤路由
    function filterAsyncRoutes(routes, roles) {
      const res = []
      routes.forEach(route => {
        const r = { ...route }
        if (hasPermission(roles, r.meta)) {
          if (r.children) {
            r.children = filterAsyncRoutes(r.children, roles)
          }
          res.push(r)
        }
      })
      return res
    }
    

    ④ 动态路由的持久化
    刷新页面后动态添加的路由会丢失,因为router.addRoute是在内存中操作,刷新后需要重新执行权限逻辑。所以需要在刷新前将用户角色等信息存储在store(或缓存)中,再次进入守卫时重新生成。

  13. 根据后端接口配置路由【实战】
    当权限粒度很细,或者菜单项完全由后端动态配置时,前端需要将后端返回的JSON格式路由数据转换成Vue Router可识别的路由配置。

    ① 为什么需要?

    • 大型CMS系统中,菜单和权限经常变化,前端重新发版成本高
    • 后端统一管理权限,前端只负责渲染
    • 实现零代码修改即可调整菜单结构

    ② 后端返回数据结构示例

    [
      {
        "path": "/dashboard",
        "name": "Dashboard",
        "component": "dashboard/index",  // 组件路径,需要映射
        "meta": { "title": "仪表盘", "icon": "dashboard" },
        "children": []
      },
      {
        "path": "/system",
        "name": "System",
        "component": "Layout",  // 布局组件
        "meta": { "title": "系统管理", "icon": "setting" },
        "children": [
          {
            "path": "user",
            "name": "User",
            "component": "system/user/index",
            "meta": { "title": "用户管理" }
          }
        ]
      }
    ]
    

    ③ 前端动态映射组件

    // 组件映射表(需要提前导入所有可能用到的组件)
    const componentMap = {
      'Layout': () => import('@/layouts/Layout.vue'),
      'dashboard/index': () => import('@/views/dashboard/index.vue'),
      'system/user/index': () => import('@/views/system/user/index.vue')
      // 更多组件...
    }
    
    function generateRoutes(backendRoutes) {
      const routes = []
      backendRoutes.forEach(route => {
        const r = {
          path: route.path,
          name: route.name,
          component: componentMap[route.component],
          meta: route.meta,
          children: route.children ? generateRoutes(route.children) : []
        }
        routes.push(r)
      })
      return routes
    }
    
    // 获取后端路由后调用
    const asyncRoutes = generateRoutes(backendData)
    asyncRoutes.forEach(route => router.addRoute(route))
    
  14. 路由优化技巧【优化】

    ① 路由懒加载与代码分割
    使用动态导入import(),webpack会自动进行代码分割,生成单独的chunk。可以为每个路由指定chunk名称,便于缓存分析。

    const routes = [
      {
        path: '/large',
        component: () => import(/* webpackChunkName: "large" */ './views/Large.vue')
      }
    ]
    

    ② 预加载(Prefetch)
    Vue Router支持router.beforeResolve结合webpackPrefetch预加载即将用到的组件。或者在用户悬停链接时预加载。

    router.beforeResolve(async (to) => {
      // 预加载目标路由组件及其异步组件
      const components = to.matched.map(record => record.components.default)
      await Promise.all(components.map(comp => comp()))
    })
    

    ③ 路由级别数据预取
    在路由守卫中预先获取数据,避免进入页面后出现空白或加载状态。可以在beforeRouteEnter或全局beforeResolve中派发store action,获取数据后再进入页面。

  15. 总结【速记版】

    • 本质:URL与组件的映射关系,实现SPA无刷新视图切换
    • 模式:Hash(兼容好)和History(URL美观,需服务器支持)
    • 原理:路由配置 → 扁平化路由表 → 响应式_route → RouterView重新渲染
    • 动态路由:id参数,通过route.params访问
    • 嵌套路由children配置,多级router-view
    • 导航守卫:全局、路由独享、组件内三级防护,用于权限控制
    • 元信息meta字段附加路由元数据
    • 懒加载:动态导入组件,优化性能
    • 编程式导航router.push()router.replace()
    • 权限控制:动态添加路由(addRoute)+ 路由守卫校验
    • 动态配置:根据后端JSON动态生成路由,实现灵活权限管理
    • 优化:预加载、数据预取、代码分割

vueRouter
http://localhost:8090/archives/vuerouter
作者
Administrator
发布于
2026年03月18日
许可协议