Vue Router 中 route 和 router 的终极区别指南
在 Vue Router 的开发中,
route和router这两个相似的名字经常让开发者混淆。今天,我们用最直观的方式彻底搞懂它们的区别!
一、最简区分:一句话理解
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 使用将更加得心应手!