Vue Router 中 route 和 router 的终极区别指南

Vue Router 中 route 和 router 的终极区别指南

在 Vue Router 的开发中,routerouter 这两个相似的名字经常让开发者混淆。今天,我们用最直观的方式彻底搞懂它们的区别!

一、最简区分:一句话理解

javascript 复制代码
// 一句话总结:
// route = 当前的路由信息(只读)------ "我在哪?"
// router = 路由的实例对象(可操作)------ "我怎么去?"

// 类比理解:
// route 像 GPS 定位信息:显示当前位置(经纬度、地址等)
// router 像导航系统:提供路线规划、导航、返回等功能

二、核心区别对比表

维度 route router
本质 当前路由信息对象(只读) 路由实例(操作方法集合)
类型 RouteLocationNormalized Router 实例
功能 获取当前路由信息 进行路由操作(跳转、守卫等)
数据流向 信息输入(读取) 指令输出(执行)
修改性 只读,不可直接修改 可操作,可修改路由状态
使用场景 获取参数、查询、路径等信息 跳转、编程式导航、全局配置

三、代码直观对比

3.1 获取方式对比

javascript 复制代码
// 选项式 API
export default {
  // route:通过 this.$route 访问
  mounted() {
    console.log(this.$route)     // 当前路由信息
    console.log(this.$router)    // 路由实例
  }
}

// 组合式 API
import { useRoute, useRouter } from 'vue-router'

export default {
  setup() {
    const route = useRoute()    // 相当于 this.$route
    const router = useRouter()  // 相当于 this.$router
    
    return { route, router }
  }
}

3.2 数据结构对比

javascript 复制代码
// route 对象的结构(简化版)
const route = {
  // 路径信息
  path: '/user/123/profile?tab=settings',
  fullPath: '/user/123/profile?tab=settings#section-2',
  
  // 路由参数(params)
  params: {
    id: '123'  // 来自 /user/:id
  },
  
  // 查询参数(query)
  query: {
    tab: 'settings'  // 来自 ?tab=settings
  },
  
  // 哈希值
  hash: '#section-2',
  
  // 路由元信息
  meta: {
    requiresAuth: true,
    title: '用户设置'
  },
  
  // 匹配的路由记录
  matched: [
    { path: '/', component: Home, meta: { ... } },
    { path: '/user/:id', component: UserLayout, meta: { ... } },
    { path: '/user/:id/profile', component: Profile, meta: { ... } }
  ],
  
  // 路由名称
  name: 'UserProfile',
  
  // 重定向的来源(如果有)
  redirectedFrom: undefined
}

// router 对象的结构(主要方法)
const router = {
  // 核心方法
  push(),        // 导航到新路由
  replace(),     // 替换当前路由
  go(),          // 前进/后退
  back(),        // 后退
  forward(),     // 前进
  
  // 路由信息
  currentRoute,  // 当前路由(相当于route)
  options,       // 路由配置
  
  // 守卫相关
  beforeEach(),
  beforeResolve(),
  afterEach(),
  
  // 其他
  addRoute(),    // 动态添加路由
  removeRoute(), // 移除路由
  hasRoute(),    // 检查路由是否存在
  getRoutes(),   // 获取所有路由
  isReady()      // 检查路由是否就绪
}

四、route:深入了解当前路由信息

4.1 主要属性详解

javascript 复制代码
// 获取完整示例
const route = useRoute()

// 1. 路径相关
console.log('path:', route.path)        // "/user/123"
console.log('fullPath:', route.fullPath) // "/user/123?name=john#about"

// 2. 参数相关(最常用!)
// params:路径参数(必选参数)
console.log('params:', route.params)    // { id: '123', slug: 'vue-guide' }
console.log('id:', route.params.id)     // "123"

// query:查询参数(可选参数)
console.log('query:', route.query)      // { page: '2', sort: 'desc' }
console.log('page:', route.query.page)  // "2"

// hash:哈希值
console.log('hash:', route.hash)        // "#section-1"

// 3. 元信息(meta)
// 路由配置中的 meta 字段
const routes = [
  {
    path: '/admin',
    component: Admin,
    meta: {
      requiresAuth: true,
      permissions: ['admin'],
      breadcrumb: '管理后台'
    }
  }
]

// 使用
if (route.meta.requiresAuth) {
  // 需要认证
}

// 4. 匹配的路由记录
route.matched.forEach(record => {
  console.log('匹配的路由:', record.path)
  // 可以访问嵌套路由的 meta
  if (record.meta.requiresAuth) {
    // 所有匹配的路由都需要认证
  }
})

// 5. 名称和来源
console.log('name:', route.name)            // "UserProfile"
console.log('redirectedFrom:', route.redirectedFrom) // 重定向来源

4.2 实际使用场景

vue 复制代码
<template>
  <!-- 场景1:根据参数显示内容 -->
  <div v-if="route.params.id">
    用户ID: {{ route.params.id }}
  </div>
  
  <!-- 场景2:根据query显示不同标签 -->
  <div v-if="route.query.tab === 'profile'">
    显示个人资料
  </div>
  <div v-else-if="route.query.tab === 'settings'">
    显示设置
  </div>
  
  <!-- 场景3:动态标题 -->
  <title>{{ pageTitle }}</title>
</template>

<script>
import { useRoute, computed } from 'vue'

export default {
  setup() {
    const route = useRoute()
    
    // 动态标题
    const pageTitle = computed(() => {
      const baseTitle = '我的应用'
      if (route.meta.title) {
        return `${route.meta.title} - ${baseTitle}`
      }
      return baseTitle
    })
    
    // 权限检查
    const hasPermission = computed(() => {
      const userRoles = ['user', 'editor']
      const requiredRoles = route.meta.roles || []
      return requiredRoles.some(role => userRoles.includes(role))
    })
    
    // 面包屑导航
    const breadcrumbs = computed(() => {
      return route.matched
        .filter(record => record.meta.breadcrumb)
        .map(record => ({
          title: record.meta.breadcrumb,
          path: record.path
        }))
    })
    
    return { route, pageTitle, hasPermission, breadcrumbs }
  }
}
</script>

五、router:路由操作和控制

5.1 核心方法详解

javascript 复制代码
const router = useRouter()

// 1. 编程式导航
// push - 添加新的历史记录
router.push('/home')                      // 路径字符串
router.push({ path: '/home' })            // 路径对象
router.push({ name: 'Home' })             // 命名路由
router.push({ 
  name: 'User', 
  params: { id: 123 }, 
  query: { tab: 'profile' },
  hash: '#section-2'
})

// replace - 替换当前历史记录(无返回)
router.replace('/login')
router.replace({ path: '/login', query: { redirect: route.fullPath } })

// go - 在历史记录中前进/后退
router.go(1)    // 前进1步
router.go(-1)   // 后退1步
router.go(-3)   // 后退3步
router.go(0)    // 刷新当前页面

// back/forward - 便捷方法
router.back()     // 后退 = router.go(-1)
router.forward()  // 前进 = router.go(1)

// 2. 动态路由管理
// 添加路由(常用于权限路由)
router.addRoute({
  path: '/admin',
  component: Admin,
  meta: { requiresAuth: true }
})

// 添加嵌套路由
router.addRoute('Admin', {
  path: 'users',
  component: AdminUsers
})

// 移除路由
router.removeRoute('admin') // 通过名称移除

// 检查路由是否存在
if (router.hasRoute('admin')) {
  console.log('管理员路由已存在')
}

// 获取所有路由
const allRoutes = router.getRoutes()
console.log('总路由数:', allRoutes.length)

// 3. 路由守卫
// 全局前置守卫
router.beforeEach((to, from, next) => {
  if (to.meta.requiresAuth && !isAuthenticated()) {
    next('/login')
  } else {
    next()
  }
})

// 全局解析守卫
router.beforeResolve((to, from) => {
  // 所有组件解析完成后调用
})

// 全局后置守卫
router.afterEach((to, from) => {
  // 路由跳转完成后调用
  logPageView(to.fullPath)
})

5.2 实际使用场景

vue 复制代码
<template>
  <div>
    <!-- 导航按钮 -->
    <button @click="goToHome">返回首页</button>
    <button @click="goToUser(123)">查看用户123</button>
    <button @click="openInNewTab">新标签打开</button>
    <button @click="goBack">返回上一步</button>
    
    <!-- 条件导航 -->
    <button v-if="canEdit" @click="editItem">编辑</button>
    
    <!-- 路由状态 -->
    <p>当前路由: {{ currentRoute.path }}</p>
    <button @click="checkRoutes">检查路由配置</button>
  </div>
</template>

<script>
import { useRouter, useRoute } from 'vue-router'

export default {
  setup() {
    const router = useRouter()
    const route = useRoute()
    
    // 1. 基本导航
    const goToHome = () => {
      router.push('/')
    }
    
    const goToUser = (userId) => {
      router.push({
        name: 'UserProfile',
        params: { id: userId },
        query: { tab: 'details' }
      })
    }
    
    const goBack = () => {
      if (window.history.length > 1) {
        router.back()
      } else {
        router.push('/')
      }
    }
    
    // 2. 条件导航
    const canEdit = computed(() => {
      return route.params.id && userStore.canEdit(route.params.id)
    })
    
    const editItem = () => {
      router.push(`/edit/${route.params.id}`)
    }
    
    // 3. 新标签页打开
    const openInNewTab = () => {
      const routeData = router.resolve({
        name: 'UserProfile',
        params: { id: 123 }
      })
      window.open(routeData.href, '_blank')
    }
    
    // 4. 动态路由管理
    const addAdminRoute = () => {
      if (!router.hasRoute('admin')) {
        router.addRoute({
          path: '/admin',
          name: 'admin',
          component: () => import('./Admin.vue'),
          meta: { requiresAdmin: true }
        })
        console.log('管理员路由已添加')
      }
    }
    
    // 5. 路由状态检查
    const checkRoutes = () => {
      console.log('当前路由:', router.currentRoute.value)
      console.log('所有路由:', router.getRoutes())
      console.log('路由配置:', router.options)
    }
    
    // 6. 路由跳转拦截
    const navigateWithConfirm = async (to) => {
      if (route.meta.hasUnsavedChanges) {
        const confirmed = await confirm('有未保存的更改,确定离开?')
        if (!confirmed) return
      }
      router.push(to)
    }
    
    // 7. 获取路由组件
    const getRouteComponent = () => {
      const matched = route.matched
      const component = matched[matched.length - 1]?.components?.default
      return component
    }
    
    return {
      currentRoute: router.currentRoute,
      goToHome,
      goToUser,
      goBack,
      canEdit,
      editItem,
      openInNewTab,
      addAdminRoute,
      checkRoutes,
      navigateWithConfirm
    }
  }
}
</script>

六、常见误区与正确用法

6.1 错误 vs 正确

javascript 复制代码
// ❌ 错误:试图修改 route
this.$route.params.id = 456  // 不会生效!
this.$route.query.page = '3' // 不会生效!

// ✅ 正确:使用 router 进行导航
this.$router.push({
  params: { id: 456 },
  query: { page: '3' }
})

// ❌ 错误:混淆使用
// 试图用 route 进行跳转
this.$route.push('/home')  // 报错!route 没有 push 方法

// ✅ 正确:分清职责
const id = this.$route.params.id    // 获取信息用 route
this.$router.push(`/user/${id}`)    // 跳转用 router

// ❌ 错误:直接修改 URL
window.location.href = '/new-page'  // 会刷新页面!

// ✅ 正确:使用 router
this.$router.push('/new-page')      // 单页应用跳转

6.2 响应式处理

vue 复制代码
<template>
  <!-- ❌ 错误:直接监听路由对象 -->
  <!-- 这种方式可能会导致无限循环 -->
  
  <!-- ✅ 正确:使用计算属性或监听器 -->
  <div>
    当前用户: {{ userId }}
    当前页面: {{ currentPage }}
  </div>
</template>

<script>
export default {
  computed: {
    // ✅ 正确:使用计算属性响应式获取
    userId() {
      return this.$route.params.id || 'unknown'
    },
    currentPage() {
      return parseInt(this.$route.query.page) || 1
    }
  },
  
  watch: {
    // ✅ 正确:监听特定参数变化
    '$route.params.id': {
      handler(newId) {
        if (newId) {
          this.loadUser(newId)
        }
      },
      immediate: true
    },
    
    // ✅ 监听整个路由变化(谨慎使用)
    $route(to, from) {
      // 处理路由变化逻辑
      this.trackPageView(to.path)
    }
  },
  
  // ✅ 使用路由守卫
  beforeRouteUpdate(to, from, next) {
    // 在同一组件内响应路由参数变化
    this.loadData(to.params.id)
    next()
  }
}
</script>

七、高级应用场景

7.1 路由元信息和权限控制

javascript 复制代码
// 路由配置
const routes = [
  {
    path: '/',
    component: Home,
    meta: { 
      title: '首页',
      requiresAuth: false 
    }
  },
  {
    path: '/dashboard',
    component: Dashboard,
    meta: { 
      title: '控制面板',
      requiresAuth: true,
      permissions: ['user']
    }
  },
  {
    path: '/admin',
    component: Admin,
    meta: { 
      title: '管理员',
      requiresAuth: true,
      permissions: ['admin'],
      breadcrumb: '管理后台'
    },
    children: [
      {
        path: 'users',
        component: AdminUsers,
        meta: { 
          title: '用户管理',
          breadcrumb: '用户管理'
        }
      }
    ]
  }
]

// 权限控制守卫
router.beforeEach((to, from, next) => {
  const isAuthenticated = checkAuth()
  const userPermissions = getUserPermissions()
  
  // 检查是否需要认证
  if (to.meta.requiresAuth && !isAuthenticated) {
    next({
      path: '/login',
      query: { redirect: to.fullPath }
    })
    return
  }
  
  // 检查权限
  if (to.meta.permissions) {
    const hasPermission = to.meta.permissions.some(perm => 
      userPermissions.includes(perm)
    )
    
    if (!hasPermission) {
      next('/403') // 无权限页面
      return
    }
  }
  
  next()
})

// 组件内使用
export default {
  setup() {
    const route = useRoute()
    const router = useRouter()
    
    // 检查当前路由权限
    const canAccess = computed(() => {
      if (!route.meta.permissions) return true
      return route.meta.permissions.some(perm => 
        userStore.permissions.includes(perm)
      )
    })
    
    // 如果没有权限,重定向
    watchEffect(() => {
      if (!canAccess.value) {
        router.replace('/unauthorized')
      }
    })
    
    return { canAccess }
  }
}

7.2 路由数据预取

javascript 复制代码
// 使用 router 和 route 配合数据预取
const router = createRouter({
  routes,
  scrollBehavior(to, from, savedPosition) {
    // 滚动行为控制
    if (savedPosition) {
      return savedPosition
    }
    return { top: 0 }
  }
})

// 组件数据预取
export default {
  async beforeRouteEnter(to, from, next) {
    // 在进入路由前获取数据
    try {
      const userData = await fetchUser(to.params.id)
      next(vm => {
        vm.user = userData
      })
    } catch (error) {
      next('/error')
    }
  },
  
  async beforeRouteUpdate(to, from, next) {
    // 路由参数变化时更新数据
    this.user = await fetchUser(to.params.id)
    next()
  }
}

7.3 路由状态持久化

javascript 复制代码
// 保存路由状态到 localStorage
const router = createRouter({
  history: createWebHistory(),
  routes
})

// 路由变化时保存状态
router.afterEach((to) => {
  localStorage.setItem('lastRoute', JSON.stringify({
    path: to.path,
    query: to.query,
    params: to.params,
    timestamp: Date.now()
  }))
})

// 应用启动时恢复状态
router.isReady().then(() => {
  const saved = localStorage.getItem('lastRoute')
  if (saved) {
    const lastRoute = JSON.parse(saved)
    // 根据保存的状态做一些处理
    console.log('上次访问:', lastRoute.path)
  }
})

// 组件内使用 route 获取状态
export default {
  setup() {
    const route = useRoute()
    const router = useRouter()
    
    // 保存表单状态到路由 query
    const saveFormState = (formData) => {
      router.push({
        query: {
          ...route.query,
          form: JSON.stringify(formData)
        }
      })
    }
    
    // 从路由 query 恢复表单状态
    const loadFormState = () => {
      if (route.query.form) {
        return JSON.parse(route.query.form)
      }
      return null
    }
    
    return { saveFormState, loadFormState }
  }
}

八、TypeScript 类型支持

typescript 复制代码
// 为 route 和 router 添加类型支持
import { RouteLocationNormalized, Router } from 'vue-router'

// 扩展 Route Meta 类型
declare module 'vue-router' {
  interface RouteMeta {
    // 自定义元字段
    requiresAuth?: boolean
    permissions?: string[]
    breadcrumb?: string
    title?: string
    keepAlive?: boolean
  }
}

// 组件内使用类型
import { useRoute, useRouter } from 'vue-router'

export default defineComponent({
  setup() {
    const route = useRoute() as RouteLocationNormalized
    const router = useRouter() as Router
    
    // 类型安全的参数访问
    const userId = computed(() => {
      // params 类型为 Record<string, string | string[]>
      const id = route.params.id
      if (Array.isArray(id)) {
        return id[0] // 处理数组情况
      }
      return id || ''
    })
    
    // 类型安全的查询参数
    const page = computed(() => {
      const pageStr = route.query.page
      if (Array.isArray(pageStr)) {
        return parseInt(pageStr[0]) || 1
      }
      return parseInt(pageStr || '1')
    })
    
    // 类型安全的导航
    const navigateToUser = (id: string) => {
      router.push({
        name: 'UserProfile',
        params: { id },  // 类型检查
        query: { tab: 'info' as const }  // 字面量类型
      })
    }
    
    return { userId, page, navigateToUser }
  }
})

九、记忆口诀与最佳实践

9.1 记忆口诀

javascript 复制代码
/*
口诀一:
route 是 "看" - 看我在哪,看有什么参数
router 是 "动" - 动去哪,动怎么去

口诀二:
route 三要素:params、query、meta
router 三动作:push、replace、go

口诀三:
读信息找 route,改路由找 router
查状态用 route,变状态用 router
*/

9.2 最佳实践清单

javascript 复制代码
const bestPractices = {
  route: [
    '✅ 使用计算属性包装 route 属性',
    '✅ 使用 watch 监听特定参数变化',
    '✅ 使用 route.meta 进行权限判断',
    '✅ 使用 route.matched 获取嵌套路由信息',
    '❌ 不要直接修改 route 对象',
    '❌ 避免深度监听整个 route 对象'
  ],
  
  router: [
    '✅ 使用命名路由代替路径字符串',
    '✅ 编程式导航时传递完整的路由对象',
    '✅ 使用 router.isReady() 等待路由就绪',
    '✅ 动态路由添加后检查是否存在',
    '❌ 不要混用 window.location 和 router',
    '❌ 避免在循环中频繁调用 router 方法'
  ],
  
  combined: [
    '✅ route 获取信息,router 执行操作',
    '✅ 使用 router.currentRoute 获取当前路由',
    '✅ 在路由守卫中结合两者进行复杂逻辑',
    '✅ 使用 TypeScript 增强类型安全'
  ]
}

总结

route 和 router 的核心区别总结:

方面 route router
角色 信息提供者 行动执行者
数据 当前路由状态快照 路由操作方法集合
修改 只读不可变 可变可操作
类比 GPS 定位信息 导航系统指令
心态 "我现在在哪?" "我要去哪里?怎么去?"

黄金法则:

  • 读信息 → 用 route
  • 做跳转 → 用 router
  • 改状态 → 通过 router 改变,从 route 读取结果

记住:route 告诉你 现在router 带你去 未来。分清它们,你的 Vue Router 使用将更加得心应手!

相关推荐
m0_471199631 小时前
【场景】如何快速接手一个前端项目
前端·vue.js·react.js
Tigger2 小时前
用 Vue 3 做了一套年会抽奖工具,顺便踩了些坑
前端·javascript·vue.js
OpenTiny社区2 小时前
OpenTiny 2025年度贡献者榜单正式公布~
前端·javascript·vue.js
biubiubiu07062 小时前
Vue脚手架创建项目记录
javascript·vue.js·ecmascript
北辰alk3 小时前
Vue 表单修饰符 .lazy:性能优化的秘密武器
vue.js
北辰alk3 小时前
`active-class`:Vue Router 链接组件的激活状态管理
vue.js
北辰alk3 小时前
Vue Router 参数传递:params vs query 深度解析
vue.js
北辰alk3 小时前
Vue 3 Diff算法革命:比双端比对快在哪里?
vue.js
boooooooom3 小时前
手写简易Vue响应式:基于Proxy + effect的核心实现
javascript·vue.js