Vue路由

目录


路由基础

SPA与路由

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
┌─────────────────────────────────────────────────────────────┐
│ SPA 应用 │
│ │
│ ┌─────────────┐ │
│ │ Browser │ │
│ ├─────────────┤ │
│ │ #/home │ ────> 渲染 Home 组件 │
│ │ #/about │ ────> 渲染 About 组件 │
│ │ #/user/1 │ ────> 渲染 User 组件 (id=1) │
│ └─────────────┘ │
│ │
│ - 页面不刷新 │
│ - URL变化触发视图更新 │
│ - 利用History API实现 │
└─────────────────────────────────────────────────────────────┘

Vue Router引入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// Vue2
import Vue from 'vue'
import VueRouter from 'vue-router'

Vue.use(VueRouter)

// Vue3
import { createApp } from 'vue'
import { createRouter, createWebHistory } from 'vue-router'

const router = createRouter({
history: createWebHistory(),
routes: []
})

const app = createApp({})
app.use(router)
app.mount('#app')

基本使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// router/index.js
// Vue2
import Vue from 'vue'
import VueRouter from 'vue-router'
import Home from '@/views/Home.vue'
import About from '@/views/About.vue'

Vue.use(VueRouter)

const routes = [
{ path: '/', redirect: '/home' },
{ path: '/home', component: Home },
{ path: '/about', component: About }
]

const router = new VueRouter({
routes
})

export default router
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// Vue3
import { createRouter, createWebHistory } from 'vue-router'
import Home from '@/views/Home.vue'
import About from '@/views/About.vue'

const routes = [
{ path: '/', redirect: '/home' },
{ path: '/home', component: Home },
{ path: '/about', component: About }
]

const router = createRouter({
history: createWebHistory(),
routes
})

export default router
1
2
3
4
5
6
7
8
9
10
11
12
<!-- App.vue -->
<template>
<div>
<nav>
<router-link to="/home">首页</router-link>
<router-link to="/about">关于</router-link>
</nav>

<!-- 路由出口 -->
<router-view></router-view>
</div>
</template>

路由配置

路由模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Vue3
import { createRouter, createWebHistory, createWebHashHistory } from 'vue-router'

// History模式 - 漂亮的URL,需要服务器配置
const router = createRouter({
history: createWebHistory(),
routes: []
})

// Hash模式 - URL带#号,无需服务器配置
const router = createRouter({
history: createWebHashHistory(),
routes: []
})

路由懒加载

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// Vue3
const routes = [
// 方式1: 动态导入
{
path: '/home',
component: () => import('@/views/Home.vue')
},

// 方式2: 命名chunk
{
path: '/about',
component: () => import(/* webpackChunkName: "about" */ '@/views/About.vue')
},

// 方式3: 预加载
{
path: '/user',
component: () => import('@/views/User.vue')
}
]

嵌套路由

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const routes = [
{
path: '/user',
component: UserLayout,
children: [
{
path: '', // /user
component: UserHome
},
{
path: 'profile', // /user/profile
component: UserProfile
},
{
path: 'settings', // /user/settings
component: UserSettings
}
]
}
]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!-- UserLayout.vue -->
<template>
<div class="user-layout">
<div class="sidebar">
<router-link to="/user">首页</router-link>
<router-link to="/user/profile">资料</router-link>
<router-link to="/user/settings">设置</router-link>
</div>
<div class="content">
<!-- 子路由出口 -->
<router-view></router-view>
</div>
</div>
</template>

命名路由

1
2
3
4
5
6
7
const routes = [
{
path: '/user/:id',
name: 'user-profile',
component: UserProfile
}
]
1
2
3
4
5
<!-- 跳转 -->
<router-link :to="{ name: 'user-profile', params: { id: 1 } }">用户资料</router-link>

<!-- JS跳转 -->
this.$router.push({ name: 'user-profile', params: { id: 1 } })

路由跳转

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!-- 基本跳转 -->
<router-link to="/home">首页</router-link>

<!-- 带参数 -->
<router-link :to="{ path: '/user', query: { id: 1 } }">用户</router-link>

<!-- 命名路由 -->
<router-link :to="{ name: 'user', params: { id: 1 } }">用户</router-link>

<!-- active类名 -->
<router-link to="/home" active-class="active">首页</router-link>

<!-- exact-active-class -->
<router-link to="/" exact active-class="exact-active">首页</router-link>

replace模式

1
2
3
4
5
<!-- replace模式 - 不留下历史记录 -->
<router-link to="/home" replace>首页</router-link>

<!-- JS -->
this.$router.replace({ path: '/home' })

append模式

1
2
3
<!-- 在当前路径后追加 -->
<router-link to="append" append>追加路径</router-link>
<!-- 如果当前是 /home,点击后跳转到 /home/append -->

路由传参

query参数

1
2
3
4
5
<!-- 跳转时传递query参数 -->
<router-link to="/user?id=1&name=zhangsan">用户</router-link>

<!-- 对象形式 -->
<router-link :to="{ path: '/user', query: { id: 1, name: 'zhangsan' } }">用户</router-link>
1
2
3
4
5
6
7
8
9
10
// 获取query参数
// Vue3
import { useRoute } from 'vue-router'

const route = useRoute()
console.log(route.query.id) // 1
console.log(route.query.name) // zhangsan

// Vue2
this.$route.query.id

params参数

1
2
3
4
5
6
7
8
9
10
11
12
// 定义路由时使用占位符
const routes = [
{ path: '/user/:id', component: User },
{ path: '/user/:id/posts/:postId', component: UserPost }
]

// 跳转时传递params
<router-link :to="{ name: 'user', params: { id: 1 } }">用户</router-link>

// 获取params
const route = useRoute()
console.log(route.params.id) // 1

props解耦

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 路由配置中开启props
const routes = [
{
path: '/user/:id',
component: User,
props: true // 开启后,route.params.id会自动作为props.id传入
},

// 静态props
{
path: '/about',
component: About,
props: { title: '关于页面' }
},

// 函数模式props
{
path: '/user/:id',
component: User,
props: (route) => ({
id: route.params.id,
name: route.query.name
})
}
]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!-- User.vue -->
<template>
<div>用户ID: {{ id }}</div>
</template>

<script>
export default {
props: {
id: {
type: String,
required: true
}
}
}
</script>

路由守卫

全局守卫

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// Vue3
import { createRouter, createWebHistory } from 'vue-router'

const router = createRouter({
history: createWebHistory(),
routes: []
})

// 全局前置守卫
router.beforeEach((to, from) => {
// to: 即将进入的目标路由
// from: 当前正要离开的路由
// 返回值:
// - false: 取消导航
// - undefined/string: 正常进行导航

// 检查是否需要登录
if (to.meta.requiresAuth && !isLoggedIn) {
return { name: 'login', query: { redirect: to.fullPath } }
}

return true
})

// 全局后置守卫
router.afterEach((to, from) => {
// 导航结束后的回调
document.title = to.meta.title || '默认标题'
})

// Vue2
const router = new VueRouter({ ... })

router.beforeEach((to, from, next) => {
if (to.meta.requiresAuth && !isLoggedIn) {
next({ name: 'login' })
} else {
next()
}
})

路由独享守卫

1
2
3
4
5
6
7
8
9
10
11
const routes = [
{
path: '/admin',
component: Admin,
beforeEnter: (to, from) => {
// 只在进入/admin时触发
// 不会在子路由触发
return true // 或 false 或 next()
}
}
]

组件内守卫

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// Vue3 - 使用onBeforeRouteUpdate
import { onBeforeRouteUpdate, onBeforeRouteLeave } from 'vue-router'

export default {
setup() {
// 路由参数变化时调用
onBeforeRouteUpdate((to, from) => {
console.log('路由参数变化:', to.params.id)
})

// 离开路由时调用
onBeforeRouteLeave((to, from) => {
const answer = window.confirm('确定要离开吗?')
if (!answer) return false
})
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// Vue2 - 组件内守卫
export default {
beforeRouteEnter(to, from, next) {
// 在渲染该组件的对应路由被确认前调用
// 不能访问this
next(vm => {
// 通过vm访问组件实例
})
},

beforeRouteUpdate(to, from) {
// 在当前路由改变,但是该组件被复用时调用
// 适用于 /user/:id 变化但组件被复用
this.userId = to.params.id
},

beforeRouteLeave(to, from) {
// 导航离开该组件的对应路由时调用
const answer = window.confirm('确定要离开吗?')
if (!answer) return false
}
}

守卫完整流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
┌──────────────────────────────────────────────────────────────┐
│ 导航守卫流程 │
├──────────────────────────────────────────────────────────────┤
│ │
│ 1. 导航触发 │
│ │ │
│ ▼ │
│ 2. 失活的组件调用 beforeRouteLeave │
│ │ │
│ ▼ │
│ 3. 全局 beforeEach │
│ │ │
│ ▼ │
│ 4. 重用的组件调用 beforeRouteUpdate │
│ │ │
│ ▼ │
│ 5. 路由配置中的 beforeEnter │
│ │ │
│ ▼ │
│ 6. 激活的组件调用 beforeRouteEnter │
│ │ │
│ ▼ │
│ 7. 全局 beforeResolve │
│ │ │
│ ▼ │
│ 8. 导航确认 │
│ │ │
│ ▼ │
│ 9. 全局 afterEach │
│ │ │
│ ▼ │
│ 10. DOM更新完毕 │
│ │
└──────────────────────────────────────────────────────────────┘

路由元信息

meta定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const routes = [
{
path: '/admin',
component: Admin,
meta: {
requiresAuth: true,
title: '管理后台',
roles: ['admin']
}
},
{
path: '/profile',
component: Profile,
meta: {
title: '个人资料'
}
}
]

访问meta

1
2
3
4
5
6
7
8
9
10
11
12
13
// 全局前置守卫中访问
router.beforeEach((to, from) => {
if (to.meta.requiresAuth) {
// 检查权限
}

// 设置页面标题
document.title = to.meta.title || '默认标题'
})

// 组件中访问
const route = useRoute()
console.log(route.meta.requiresAuth)

编程式导航

push和replace

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 字符串路径
this.$router.push('/home')

// 对象路径
this.$router.push({ path: '/home' })

// 命名路由 + params
this.$router.push({ name: 'user', params: { id: 1 } })

// 带query参数
this.$router.push({ path: '/user', query: { id: 1 } })

// replace模式
this.$router.replace({ path: '/home' })

go

1
2
3
4
// 前进或后退指定步数
this.$router.go(1) // 前进1步
this.$router.go(-1) // 后退1步
this.$router.go(0) // 刷新当前页

back

1
2
// 后退一步
this.$router.back()

forward

1
2
// 前进一步
this.$router.forward()

promise支持

1
2
3
4
5
6
7
8
// Vue3 - push返回promise
try {
await router.push('/home')
} catch (err) {
if (err.name !== 'NavigationDuplicated') {
throw err
}
}

路由进阶

滚动行为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// Vue3
const router = createRouter({
history: createWebHistory(),
routes: [],

scrollBehavior(to, from, savedPosition) {
// savedPosition只在popstate导航下可用

// 滚动到锚点
if (to.hash) {
return {
el: to.hash,
behavior: 'smooth'
}
}

// 滚动到指定位置
if (savedPosition) {
return savedPosition
}

// 滚动到顶部
return { top: 0 }
}
})

路由懒加载失败处理

1
2
3
4
5
6
7
8
9
10
11
12
const routes = [
{
path: '/slow',
component: () => ({
component: import('./SlowComponent.vue'),
loading: LoadingComponent,
error: ErrorComponent,
delay: 200,
timeout: 3000
})
}
]

动态路由

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 添加路由
router.addRoute({ path: '/new', component: NewComponent })

// 添加嵌套路由
router.addRoute('parent', {
path: 'child',
component: ChildComponent
})

// 删除路由
router.removeRoute('user-profile')

// 路由存在检查
if (router.hasRoute('user-profile')) {
// 路由存在
}

获取当前路由信息

1
2
3
4
5
6
7
8
9
10
11
12
13
// Vue3 Composition API
import { useRoute, useRouter } from 'vue-router'

const route = useRoute()
const router = useRouter()

// route.fullPath - 完整路径
// route.path - 路径部分
// route.params - 参数
// route.query - 查询参数
// route.hash - hash值
// route.name - 路由名称
// route.meta - 元信息

History API

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// Vue2 History模式
const router = new VueRouter({
mode: 'history',
routes: []
})

// Vue3 History配置
const router = createRouter({
history: createWebHistory('/base-url'),
routes: []
})

// Hash模式
const router = createRouter({
history: createWebHashHistory(),
routes: []
})

导航故障处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// Vue3
import { isNavigationFailure, NavigationFailureType } from 'vue-router'

try {
await router.push('/home')
} catch (error) {
if (isNavigationFailure(error, NavigationFailureType.redirected)) {
// 重定向导致的导航失败
} else if (isNavigationFailure(error, NavigationFailureType.aborted)) {
// 导航被阻止
} else if (isNavigationFailure(error, NavigationFailureType.cancelled)) {
// 导航被取消
} else if (isNavigationFailure(error, NavigationFailureType.duplicated)) {
// 重复导航
}
}

Vue Router 4新特性

与Vue Router 3对比

特性 Vue Router 3 Vue Router 4
Vue版本 Vue2 Vue3
安装方式 Vue.use() app.use()
创建方式 new VueRouter() createRouter()
History new VueRouter({ mode }) createWebHistory()
通配符 path: ‘*’ path: ‘/:pathMatch(.*)’
导航守卫 next() 返回true/false/promise
路由懒加载 component: resolve => component: () => import()

常用迁移

1
2
3
4
5
6
7
8
9
10
// Vue3 - 动态路由参数变化
// Vue3需要使用 watch 或者 beforeRouteUpdate
import { watch } from 'vue'
import { useRoute } from 'vue-router'

const route = useRoute()

watch(() => route.params.id, (newId) => {
// 处理id变化
})
1
2
3
// Vue3 - router.currentRoute
const currentRoute = router.currentRoute.value
console.log(currentRoute.value.path)

总结

路由核心概念

概念 说明
router-link 声明式导航
router-view 路由出口
$router 路由实例,导航方法
$route 当前路由信息
导航守卫 控制路由访问权限
路由懒加载 优化首屏加载

路由守卫应用场景

守卫 场景
beforeEach 登录验证、权限检查
beforeEnter 路由特定检查
beforeRouteEnter 获取数据、实例访问
beforeRouteUpdate 复用组件的参数变化
beforeRouteLeave 确认离开、清理工作
afterEach 页面标题、统计

相关链接