12.Vue路由管理

Vue路由是用来管理页面切换或跳转的一种方式

Vue Router的安装与简单使用

Vue Router是Vue官方的路由管理器,与Vue框架本身深度契合,Vue Router主要包含以下功能:

  • 路由支持嵌套
  • 可以模块化地进行路由配置
  • 支持路由参数、查询和通配符
  • 提供了视图过度效果
  • 能够精准地进行导航控制

Vue Router的安装

与Vue框架本身一样,Vue Router支持使用CDN的方式引入,也支持使用NPM方式的安装

1
2
3
4
# cdn引入
https://cdn.jsdelivr.net/npm/vue-router@4.2.5/dist/vue-router.global.prod.js
# 本节采用npm安装
npm install vue-router@4 -s

安装完成后在项目package.json文件中查看是否添加

1
2
3
4
"dependencies": {
-----
"vue-router": "^4.2.5"
},

一个简单的Vue Router使用示例

在工程的components文件夹下新建两个文件,分别命为Demo1.vueDemo2.vue,在示例中编写如下示例代码:

为什么以下文件命名为DemoOneDemoTwo而不是Demo1Demo2

根据 Vue.js 的官方规范,组件的名字应该是多个单词组合而成,以便提高可读性和可维护性

1
2
# 如果你的组件名是 Demo1,ESLint 将会给出这个错误。你可以将组件名修改为一个多单词组合,比如 DemoOne,DemoComponent,或者其他更能表达组件功能的名字
1:1 error Component name "Demo1" should always be multi-word vue/multi-word-component-names

DemoOne.vue

1
2
3
4
5
6
7
8
9
10
<template>
<div>
<h1>示例页面1</h1>
</div>
</template>
<script>
export default {

}
</script>

DemoTwo.vue

1
2
3
4
5
6
7
8
9
10
<template>
<div>
<h1>示例页面2</h1>
</div>
</template>
<script>
export default {

}
</script>

修改App.vue文件如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<template>
<div>
<h1>HelloWorld</h1>
<p>
<!-- router-link是路由跳转组件,用to来指定要跳转的路由 -->
<router-link to="/demo1">页面1</router-link>
<br>
<router-link to="/demo2">页面2</router-link>
</p>
<!-- router-view是路由的页面出口,路由匹配到的组件会渲染在此 -->
<router-view></router-view>
</div>
</template>

<script>
export default {
name: 'App',
components: {
}
}
</script>

如上代码所示,router-link组件是一个自定义的链接组件,它比常规的a标签要强大很多,其允许在不重新加载页面的情况下更改页面的URL。router-view用来渲染与当前URL对应的组件,我们可以将其放在任何位置,例如带顶部导航栏的应用,其页面主题内容部分就可以放置router-view组件,通过导航栏上按钮的切换来替换内容组件。

修改main.js文件,在其中进行路由的定义与注册,示例代码如下:

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
// 导入Vue框架中的createApp方法
import { createApp } from 'vue'
// 导入Vue Router模块中的createRouter, createWebHashHistory方法
// 是createWebHashHistory不是createWebHistory。哈希模式不会导致浏览器向服务器发送新的请求,因此在单页应用中非常适用
// 哈希模式 http://example.com/#/user/123
// 历史模式 http://example.com/user/123
import { createRouter, createWebHashHistory } from 'vue-router'
// 导入我们自定义的根组件
import App from './App.vue'
// 导入路由需要用到的自定义组件
import Demo1 from './components/DemoOne.vue'
import Demo2 from './components/DemoTwo.vue'

// createApp(App).mount('#app')
// 挂载根组件
const app = createApp(App)
// 定义路由
const routes = [
{path: '/demo1',component:Demo1},
{path: '/demo2',component:Demo2},
]
// 创建路由对象
const router = createRouter({
history: createWebHashHistory(),
routes: routes
})
// 注册路由
app.use(router)
// 进行应用挂载
app.mount("#app")

运行上面代码,单击页面上的两个按钮,可以看到对应内容组件也会发生切换。

Vue Route体验

带参数的动态路由

不同的路由可以匹配到不同的组件,从而实现页面的切换。例如对于“用户中心”这类页面组件,不同的用户渲染的信息是不同的,这时就可以通过路由添加参数来实现。

路由参数匹配

编写一个示例的用户中心组件,直接通过解析路由中的参数来显示当前用户的昵称和编号。在工程的components文件夹下新建一个名为UserName.vue文件,其中编写如下代码:

1
2
3
4
5
6
7
8
9
10
<template>
<h1>User Profile</h1>
<h1>姓名:{{ $route.params.username }}</h1>
<h2>ID:{{ $route.params.id }}</h2>
</template>
<script>
export default {

}
</script>

在组件内部使用$route属性获取全局的路由对象,路由中定义的参数可以在此对象的params属性中获取到。

main.js中添加路由如下:

1
2
3
4
5
6
7
// ------------省略---------
import User from './components/UserName.vue'
const routes = [
{path: '/demo1',component:Demo1},
{path: '/demo2',component:Demo2},
{path:'/user/:username/:id',component:User}
]

在定义路径path时,使用:来标记参数,如以上定义的路径,username和id都是路由参数,以下路径会被自动匹配

1
/user/小王/888888

其中“小王”会被解析到路由的username属性,“888888”会被解析到路由的id属性

运行VUE工程,尝试在浏览器输入以下地址:

1
http://localhost:8080/#/user/小王/888888

解析路由中的参数

路由的哈希模式(hash mode)和历史模式(history mode)是在前端路由中常见的两种模式,它们在处理 URL 路由时有一些区别。

当你在创建 Vue Router 实例时,你可以通过使用createWebHashHistorycreateWebHistory方法来选择使用哈希模式或历史模式。

  1. 哈希模式(Hash Mode):
    • 在哈希模式中,URL 中的路由路径会被包含在哈希符号(#)之后,例如:http://example.com/#/user/123
    • 哈希模式不会导致浏览器向服务器发送新的请求,因此在单页应用中非常适用。
    • 哈希模式的路由变化不会影响浏览器的历史记录,因此在回退和前进时不会触发新的请求。
  2. 历史模式(History Mode):
    • 在历史模式中,URL 中的路由路径不包含哈希符号,而是直接使用斜杠分隔的路径,例如:http://example.com/user/123
    • 历史模式会使用 HTML5 的 history API 来管理路由,因此可以创建“真实”的 URL,而不是带有哈希的 URL。
    • 历史模式下,当用户在浏览器中点击前进或后退按钮时,会向服务器发送新的请求,因此需要在服务器端进行相应的配置来支持这种模式。

选择使用哈希模式还是历史模式通常取决于你的应用程序的需求和服务器的支持情况。在大多数情况下,哈希模式是更容易实现的选择,因为它不需要服务器端的特殊配置,并且可以在不同的环境中工作得更好。历史模式通常需要服务器端的配置来支持前端路由的重定向。

注意:在使用带参数的路由时,对应相同组件的路由在进行导航切换时,相同的组件并不会被销毁在创建,这种复用机制使得页面的加载效率更高。但这也表面,页面切换时,组件的生命周期方法都不会再次调用,如果需要通过路由参数来请求数据,之后渲染页面需要特别注意,不能在生命周期方法进行数据请求逻辑。

例如修改App.vue组件的模板如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<template>
<div>
<h1>HelloWorld</h1>
<p>
<!-- router-link是路由跳转组件,用to来指定要跳转的路由 -->
<router-link to="/user/小王/888888">小王</router-link>
<br>
<router-link to="/user/小李/666666">小李</router-link>
</p>
<!-- router-view是路由的页面出口,路由匹配到的组件会渲染在此 -->
<router-view></router-view>
</div>
</template>

<script>
export default {
name: 'App',
components: {
}
}
</script>

修改UserName.vue代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
<template>
<h1>User Profile</h1>
<h1>姓名:{{ $route.params.username }}</h1>
<h2>ID:{{ $route.params.id }}</h2>
</template>
<script>
export default {
mounted() {
alert(`组件加载,请求数据。路由参数为name:${this.$route.params.username}id:${this.$route.params.id}`)
},
}
</script>

运行上面代码可以看到,单击页面上的链接进行组件切换时,UserName组件中显示的用户名和编号都会实时的进行刷新,但是alert弹窗只有在UserName组件第一次加载时才会弹出,后续不会在弹出。对于这种场景,我们可以采用导航守卫的方式来处理,每次路由参数有更新,都会回调守卫函数,修改UserName.vue组件中的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
<template>
<h1>User Profile</h1>
<h1>姓名:{{ $route.params.username }}</h1>
<h2>ID:{{ $route.params.id }}</h2>
</template>
<script>
export default {
beforeRouteUpdate(to,from) {
console.log(to,from);
alert(`组件加载,请求数据。路由参数为name:${to.params.username}id:${to.params.id}`)
},
}
</script>

再次运行代码,当同一个路由参数发生变化时,也会有alert弹窗提示。beforeRouteUpdate函数在路由将要更新时会调用,其会传入两个参数,to是更新后的路由对象,from是更新前的路由对象。

beforeRouteUpdate 是 Vue Router 中的一个导航守卫,用于在当前路由更新时触发。它是在当前路由改变,但是该组件被复用时调用,例如从 /user/1 导航到 /user/2,由于两个路由都渲染了同一个组件,这时就会触发 beforeRouteUpdate 导航守卫。

beforeRouteUpdate

路由匹配的语法规则

在进行路由参数匹配时,Vue Router允许参数内部使用正则表达式来进行匹配。首先来看一个例子。在上一节中,我们提供了UserName组件做路由示范,将其修改如下:

1
2
3
4
5
6
7
8
9
<template>
<h1>用户中心</h1>
<h1>姓名:{{ $route.params.username }}</h1>
<!-- <h2>ID:{{ $route.params.id }}</h2> -->
</template>
<script>
export default {
}
</script>

同时,在components文件夹下新建一个名为UserSetting.vue的文件,编写内容如下:

1
2
3
4
5
6
7
8
9
<template>
<h1>用户设置</h1>
<!-- <h1>姓名:{{ $route.params.username }}</h1> -->
<h2>ID:{{ $route.params.id }}</h2>
</template>
<script>
export default {
}
</script>

我们将UserName组件作为用户中心页面,UserSetting组件作为用户设置页面来使用,修改main.js文件中定义路由

1
2
3
4
5
6
7
8
9
10
// --------------------
import UserSetting from './components/UserSetting.vue'
// ---------------------
const routes = [
{path: '/demo1',component:Demo1},
{path: '/demo2',component:Demo2},
{path:'/user/:username',component:User},
{path:'/user/:id',component:UserSetting},
]
// ----------------

你会发现,上面的代码中定义的两个路由除了参数名不同外,格式完全一样。这种情况下,我们无法访问用户设置页面,所有符合UserSetting组件的路由同时也符合UserName组件。根据优先匹配原则,所以一直访问到的是UserName组件。为了解决这一问题,最简单的方法是加一个静态前缀路径加以区分。例如:

1
2
{path:'/user/info/:username',component:User},
{path:'/user/setting/:id',component:UserSetting},

这是一个好方法,但并不是唯一的方法。对于本示例来说,用户中心页面与用户设置页面需要的参数类型有着明显差异,用户编号必须是数值,用户必须纯数字。因此可以通过正则表达式来约束不同类型的参数匹配到不同的路由。示例如下:

1
2
{path:'/user/:username',component:User},
{path:'/user/:id(\\d+)',component:UserSetting},

这样,对于“/user/666666”这样的纯数字路由就会匹配到UserSetting组件,“/user/小王”这样的路由就会匹配到User组件。

在正则表达式中,* 可以用来匹配0个或多个,+ 可以用来匹配1个或多个。在路由定义时使用这两个符合可以实现多级参数。在components文件夹下新建一个名为UserCategory.vue的示例组件,编写代码如下:

1
2
3
4
5
6
7
8
<template>
<h1>类别</h1>
<h2>{{ $route.params.cat }}</h2>
</template>
<script>
export default {
}
</script>

main.js增加路由如下:

1
2
import UserCategory from './components/UserCategory.vue'
{path:'/category/:cat*',component:UserCategory},

我们采用多级匹配的方式来定义路由时,路由中传递的参数会自动转黄成一个数组,例如路由/category/一级/二级/三级可以匹配到上面定义的路由,匹配成功后,cat参数为一个数组,其中数据为["一级","二级","三级"]

1
2
3
4
## 访问 http://localhost:8080/#/category/qqq/www/eee
## 页面显示
类别
[ "qqq", "www", "eee" ]

有时候页面组件所需要的参数并不都是必传的,以用户中心页面为例,当传入用户名参数时,其需要渲染登陆后的用户中心状态,当没有传用户名参数时,其可能需要渲染未登录时的状态。这时,可以将username参数定义为可选,示例如下:

1
{path:'/user/:username?',component:User},

访问:http://localhost:8080/#/user/

参数被定义可选后,路由中不包含此参数的时候也可以正常匹配到指定组件。

路由的嵌套

前面定义了很多路由,但是真正渲染路由的地方只有一个,即只有一个<router-view></router-view>出口,这类路由实际上都是顶级路由。在实际开发中,我们的项目可能非常复杂,除了根组件中需要路由外,一些子组件中可能也需要路由,Vue Router提供了嵌套路由来支持这类场景。

以之前创建的UserName组件为例,假设组件中有一部分用来渲染用户好友列表,这部分也可以用组件来完成。首先components文件夹下新建一个名为UserFriends.vue的文件,编写代码如下

1
2
3
4
5
6
<template>
<h1>好友列表</h1>
<h2>好友人数:{{ $route.params.count }}</h2>
</template>
<script>
</script>

UserFriends组件只会在用户中心使用,可以将其作为一个子路由进行定义。首先修改UserName.vue代码如下

1
2
3
4
5
6
7
8
9
<template>
<h1>用户中心</h1>
<h1>姓名:{{ $route.params.username }}</h1>
<router-view></router-view>
</template>
<script>
export default {
}
</script>

需要注意,UserName组件本身也是由路由管理的,在UserName组件内部在使用的<router-view></router-view>标签实际定义的是二级路由的页面出口。在main.js中定义二级路由如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import UserFriends from './components/UserFriends.vue'

const routes = [
{
path:'/user/:username?',
component:User,
children:[
{
path:'friends/:count',
component:UserFriends
}
]
},
]

每个路由对象本身也可以定义子路由对象,可以根据自己的需要来定义路由嵌套的层数,通过路由嵌套,可以更换地对路由进行分层管理。如以上代码所示,当我们访问以下路径时

1
http://localhost:8080/#/user/小王/friends/369

路由嵌套

页面导航

导航本身是指页面间的跳转和切换,router-link组件就是一种导航组件。我们可以设置其属性to来指定要执行的路由。除了使用router-link组件外,还有其他的方式进行路由控制,任意可以添加交互方法的组件都可以实现路由管理。

使用路由的方法

温故而知新:导入ElementPlus

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
41
42
43
44
45
46
47
48
49
50
// npm 安装
npm install element-plus --save
// 导入
// 导入Vue框架中的createApp方法
import { createApp } from 'vue'
// 导入Vue Router模块中的createRouter, createWebHashHistory方法
import { createRouter, createWebHashHistory } from 'vue-router'
// 导入我们自定义的根组件
import App from './App.vue'
// 导入路由需要用到的自定义组件
import Demo1 from './components/DemoOne.vue'
import Demo2 from './components/DemoTwo.vue'
import User from './components/UserName.vue'
import UserSetting from './components/UserSetting.vue'
import UserCategory from './components/UserCategory.vue'
import UserFriends from './components/UserFriends.vue'
// 导入ElementPlus
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'

// 挂载根组件
const app = createApp(App)
// 定义路由
const routes = [
{path: '/demo1',component:Demo1},
{path: '/demo2',component:Demo2},
{
path:'/user/:username?',
component:User,
children:[
{
path:'friends/:count',
component:UserFriends
}
]
},
{path:'/user/:id(\\d+)',component:UserSetting},
{path:'/category/:cat*',component:UserCategory},
]
// 创建路由对象
const router = createRouter({
history: createWebHashHistory(),
routes: routes
})
// 注册路由
app.use(router)
// 注册ElementPlus
app.use(ElementPlus)
// 进行应用挂载
app.mount("#app")

当我们成功向Vue应用注册路由后,在任何Vue实例中,都可以通过$route属性访问路由对象。通过调用路由对象的push方法可以向history栈中添加一个新的记录。也就是说,用户可以通过浏览器的返回按钮返回上一个路由的URL。

首先,修改App.vue文件代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<template>
<div>
<h1>HelloWorld</h1>
<p>
<button @click="toUser">用户中心</button>
</p>
<router-view></router-view>
</div>
</template>

<script>
export default {
methods: {
toUser(){
this.$router.push({
path:"/user/小王"
})
}
},
}
</script>

如上代码所示,我们使用按钮组件代替之前的router-link组件,在按钮的单击方法中进行路由的跳转操作。push方法可以接受一个对象,对象中通过path属性配置其URL路径。push方法也支持直接传入一个字符串作为URL路径,代码如下:

1
this.$router.push("/user/小王")

也可以通过路由名加参数的方式让Vue Router自动生成URL,要使用这种方法进行路由跳转,在定义路由的时候需要对路由进行命名,代码如下:

1
2
3
4
5
6
7
const routes = [
{
path:'/user/:username?',
name:'user',
component:User,
}
]

之后,可以使用如下方式进行路由跳转:

1
2
3
4
5
6
7
8
9
methods: {
toUser(){
this.$router.push({
name:'user',
params:{
username:'小王'
}
})
}

如果路由需要查询参数,可以通过query属性进行设置,示例如下:

1
2
3
4
5
6
7
// 会被处理成:/user?name=xixi
this.$router.push({
path:'/user',
query:{
name:'xixi'
}
})

需要注意,在调用push方法配置路由对象时,如果设置了path属性,则params属性会被自动忽略push方法本身也会返回一个Promise对象,可以用其来处理路由跳转成功之后的逻辑,示例如下:

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
<template>
<div>
<h1>HelloWorld</h1>
<p>
<el-button type="primary" @click="toUser">用户中心</el-button>
</p>
<router-view></router-view>
</div>
</template>

<script>
export default {
methods: {
toUser(){
this.$router.push({
path:"/user/小王"
}).then(()=> {
this.$message({
message:"跳转成功",
type:"success",
})
})
}
},
}
</script>

push方法

导航历史控制

当我们使用router-link组件或push方法切换页面时,新的路由实际上会被放入history导航栈中,用户可以灵活地使用浏览器地前进和后退功能在导航栈路由中进行切换。对于有些场景,我们不希望导航栈中地路由增加,这时可以配置replace参数或直接调用replace方法来进行路由跳转,这种方式跳转的页面会直接替换当前的页面,即跳转前页面的路由从导航栈中删除。

在 Vue Router 中,this.$router.replace() 是一个非常有用的方法,它允许您在不留下历史记录的情况下更改当前的路由。这意味着用户将无法使用浏览器的后退按钮返回到被替换的路由。下面介绍几个 this.$router.replace() 的常见用法:

1
2
3
4
5
6
this.$router.push({
path:"/user/小王"
})
this.$router.replace({
path:'/user/小王11'
})

在使用 this.$router.replace() 时,请确保理解其与 this.$router.push() 的不同之处:replace 不会留下历史记录,而 push 会。这在处理用户导航和重定向时是一个重要的考虑因素。

Vue Router提供了另一个方法,让我们可以灵活地选择跳转到导航栈中的某个位置,示例如下:

1
2
3
4
5
6
// 跳转到后1个记录
this.$router.go(1)
// 跳转到后2个记录
this.$router.go(2)
// 跳转到后3个记录
this.$router.go(3)

关于路由的命名

在定义路由时,除了path之外,还可以设置name属性,name属性为路由提供了名称,使用名称进行路由切换比直接使用path进行切换有明显的优势:避免硬编码URL、可以自动处理参数的编码等。

使用名称进行路由切换

与使用path路径进行路由切换类似,router-link组件和push方法都可以根据名称进行路由切换。以前编写的代码为例,定义用户中心名称为user,使用如下方法可以直接进行切换:

1
2
3
4
5
6
this.$router.push({
name:'user',
params:{
username:'小王'
}
})

使用router-link组件切换的示例如下:

1
<router-link :to="{name:'user',params:{ username:'小王'}}">小王</router-link>

路由视图命名

路由视图命名是指router-view组件进行命名,router-view组件用来定义路由组件的出口,前面我们讲过,路由支持嵌套,router-view可以进行嵌套。通过嵌套,允许我们的Vue应用中出现多个router-view组件。但是对于有些场景,可能需要同级地展示多个路由视图,例如顶部导航区和主内容区两部分都需要使用路由组件,这时候就需要同级的使用router-view组件,要定义同级的每个router-view要展示的组件,可以对其进行命名。

修改App.vue文件,将页面布局分为头部和内容主体两部分,代码如下:

1
2
3
4
5
6
7
8
9
10
<template>
<el-container>
<el-header height="80px">
<router-view name="topBar"></router-view>
</el-header>
<el-main>
<router-view name="main"></router-view>
</el-main>
</el-container>
</template>

在mian.js文件中定义一个新的路由,设置如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// .........
import User from './components/UserName.vue'
import UserSetting from './components/UserSetting.vue'
// ...........
const routes = [
// ...........
{
path:'/home/:username/:id',
components:{
topBar:User,
main:UserSetting
}
},
]
// .........

这是一个具名视图的配置,允许您在同一个路由下渲染多个组件。在这个例子中,/home/:username/:id 路径将会在名为 topBar<router-view> 中渲染 User 组件,在名为 main<router-view> 中渲染 UserSetting 组件。

之前定义路由时,一个路径只对应一个组件,其实也可以通过components来设置一组组件,components需要设为一个对象,其中的键表示页面中路由视图的名称值为要渲染的组件。在上面的例子中,页面的头部会被渲染为User组件,主体部分会被渲染为UserSetting组件

访问:http://localhost:8080/#/home/小王/888888

路由视图命名

需要注意,对于没有命名的router-view组件,其名字会被默认分配为default,如果编写组件模板如下:

1
2
3
4
5
6
7
8
9
10
<template>
<el-container>
<el-header height="80px">
<router-view name="topBar"></router-view>
</el-header>
<el-main>
<router-view></router-view>
</el-main>
</el-container>
</template>

使用如下方式定义路由效果是一样的:

1
2
3
4
5
6
7
{
path:'/home/:username/:id',
components:{
topBar:User,
default:UserSetting
}
},

温馨提示:在嵌套的子路由中,也可以使用视图命名路由,对于结构复杂的页面,可以先将其按照模块进行拆分,梳理清晰路由的组织关系在进行开发。

使用别名

别名提供了一种路由路径映射的方式,也就说我们可以自由地将组件映射到一个任意地路径上,而不用受到嵌套结构的限制。

首先,尝试为一个简单的一级路由来设置别名,修改用户设置页面的路由定义如下:

1
{path:'/user/:id(\\d+)',component:UserSetting, alias:'/setting/:id'},

之后,下面两个路径的页面渲染效果将完全一样

1
2
http://localhost:8080/#/user/666
http://localhost:8080/#/setting/666

需要注意,别名和重定向并不完全一样别名不会改变用户在浏览器中输入的路径本身,对于多级嵌套的路由来说,可以使用别名在路径上对其进行简化。如果原路由有参数配置,一定要注意别名也需要对应地包含这些参数。在为路由配置别名时,alias属性可以直接设置为别名字符串,也可以设置为数组相同配置一组别名,例如:

1
{path:'/user/:id(\\d+)',component:UserSetting, alias:['/setting/:id','/s/:id']},

路由重定向

重定向也是通过路由配置来完成的,与别名的区别在于,重定向会将当前路由映射到另一个路由上,页面的URL会产生改变。例如当用户访问路由/d/1时,需要页面渲染/demo1路由对应的组件,配置如下:

1
2
3
4
const routes = [
{path: '/demo1',component:Demo1},
{path: '/d/1',component:'/demo1'},
]

redirect也支持配置为对象,设置对象的name属性可以直接指定命名路由,例如:

1
2
3
4
const routes = [
{path: '/demo1',component:Demo1,name:'Demo'},
{path: '/d/1',redirect:{name:'Demo'}},
]

上面的代码都是采用静态的方式配置路由重定向的,在实际开发中,更多的时候会采用动态的方式配置重定向,例如对于需要用户登录才能访问的页面,当未登录的用户访问此路由时,我们会自动将其重定向到登录页面,下面的代码将模拟这一过程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const routes = [
{path: '/demo1',component:Demo1,name:'Demo'},
{path: '/demo2',component:Demo2},
{path: '/d',redirect:to => {
console.log(to); // to是路由对象
// 随机数模拟登录状态
let login = Math.random() > 0.5
if (login) {
return { path:'/demo1'}
}else{
return { path:'/demo2'}
}
}
},
]

关于路由传参

通过前面的学习,对于Vue Router的基本使用已经有了初步的了解,在进行路由跳转时,可以通过参数的传递来进行后续的逻辑处理。在组件内部,之前使用$route.params的方式来获取路由传递的参数,这种方式虽然可行,但组件与路由紧紧地耦合在了一起,并不利于组件地复用性

路由地另一种传参方式——使用属性的方式进行参数传递。

还记得我们编写的用户设置页面是如何获取路由传递的id参数的吗?代码如下:

1
2
3
4
5
6
7
8
9
<template>
<h1>用户中心</h1>
<h1>姓名:{{ $route.params.username }}</h1>
<router-view></router-view>
</template>
<script>
export default {
}
</script>

由于之前在组件的模板内部使用了$route属性,这导致此组件的通用性大大降低,首先将其所有耦合路由的地方去除掉,修改如下:

1
2
3
4
5
6
7
8
9
10
11
<template>
<h1>用户设置</h1>
<h2>ID:{{ id }}</h2>
</template>
<script>
export default {
props:[
'id'
]
}
</script>

现在,UserSetting组件能够通过外部传递的属性来做内部逻辑,后面需要做的只是将路由的传参映射到外部属性上。Vue Router默认支持这一功能。路由配置方式如下:

1
2
3
const routes = [
{path:'/user/:id(\\d+)',component:UserSetting, props:true},
]

在定义路由时,将props设置为true,则路由中传递的参数会自动映射到组件定义的外部属性,使用十分方便。

对于有多个页面出口的同级命名视图,我们需要对每个视图的props单独进行设置,示例如下:

1
2
3
4
5
6
7
8
{
path:'/home/:username/:id',
components:{
topBar:User,
default:UserSetting
},
props:{topBar:true,default:true}
},

如果组件内部需要的参数与路由本身并没有直接关系,也可以将props设置为对象,此时props设置的数据将原样传递给组件外部属性,例如:

1
{path:'/user/:id(\\d+)',component:UserSetting, props:{id:'000'}},

如以上代码所示,此时路由中的参数将被弃用,组件中获取到的id属性值将固定为“000”

props还有一种更强大的使用方式,可以直接将其设置为一个函数,函数中返回要传递到组件的外部属性对象,这种方式的动态性很好,示例如下:

1
2
3
4
5
6
{path:'/user/:id(\\d+)',component:UserSetting, props:route => {
return {
id:route.params.id,
other:'other'
}
}},

路由导航守卫

顾名思义,导航守卫主要作用是在进行路由跳转时决定通过此次跳转或拒绝此次跳转。在Vue Router中有多种方式来定义导航守卫。

定义全局的导航守卫

在main.js文件中,我们使用createRouter方法来创建路由实例,此路有实例可以使用beforeEach方法来注册全局的前置导航守卫,之后当触发导航跳转时,都会被此导航守卫捕获,示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
// 创建路由对象
const router = createRouter({
history: createWebHashHistory(),
routes: routes // 我们定义的路由配置对象
})
router.beforeEach((to,from) => {
console.log("将要跳转的路由对象",to) // 将要跳转的路由对象
console.log("将要离开的路由对象",from); // 将要离开的路由对象
return false // 返回true表示允许此次跳转,返回false表示禁止此次跳转
})
// 注册路由
app.use(router)

当注册的beforeEach方法返回的是布尔值时,其用来决定是否允许此次导航跳转,如以上代码所示,所有的路由跳转都将被禁止。

更多的时候,我们会在beforeEach方法中返回一个路由配置对象来决定要跳转的页面,这种方式更加灵活,例如可以将登录状态校验的逻辑放在全局的前置守卫中处理,非常方便。示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const routes = [

{path:'/user/:id(\\d+)',name:'setting',component:UserSetting, props:true},

]

// 创建路由对象
const router = createRouter({
history: createWebHashHistory(),
routes: routes // 我们定义的路由配置对象
})
router.beforeEach((to,from) => {
console.log("将要跳转的路由对象",to) // 将要跳转的路由对象
console.log("将要离开的路由对象",from); // 将要离开的路由对象
if (to.name != 'setting'){ // 防止无限循环
return {name:'setting',params:{id:"000"}} // 返回要跳转到的路由
}
})

与定义全局前置守卫类似,也可以注册全局的导航后置回调。与前置守卫不同的是,后置回调不会改变导航本身,但是其对页面的分析和监控十分有用。示例如下:

1
2
3
4
5
6
7
8
9
10
11
// 创建路由对象
const router = createRouter({
history: createWebHashHistory(),
routes: routes // 我们定义的路由配置对象
})
router.afterEach((to,from,failure) => {
console.log("跳转结束")
console.log(to)
console.log(from)
console.log(failure)
})

路由实例的afterEach方法中设置的回调函数除了接收tofrom参数外,还会接受一个failure参数,通过它开发者可以对导航的异常信息进行记录

为特定的路由注册导航守卫

如果只有特定的场景需要在页面跳转过程中实现相关逻辑,也可以为指定的路由注册导航守卫。有两种注册方式,一种是在配置路由时定义,另一种是在组件中进行定义

在对导航进行配置时,可以直接为其设置beforeEnter属性,示例如下:

1
2
3
4
5
6
const routes = [
{path: '/demo1',component:Demo1,name:'Demo',beforeEach:router => {
console.log(router);
return false
}},
]

如上代码所示,当用户访问/demo1路由对应的组件时都会被拒绝掉。需要注意,beforeEach设置的守卫只有在进入路由时会触发,路由的参数变化并不会触发此守卫。

在编写组件时,也可以实现一些方法来为组件定制守卫函数,示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<template>
<h1>示例页面1</h1>
</template>

<script>
export default {
beforeRouteEnter(to,from){
console.log(to,from,"前置守卫");
return true
},
beforeRouteUpdate(to,from){
console.log(to,from,"路由参数有更新时的守卫");
},
beforeRouteLeave (to, from) {
console.log(to,from,"离开页面");
}
}
</script>

如以上代码所示,beforeRouteEnter是组件的导航前置守卫,在通过路由将要切换到当前组件时被调用,在这个函数中,可以做拦截操作,也可以做重定向操作,需要注意此方法只有在第一次切换此组件时会被调用,路由参数的变化不会重复调用此方法。beforeRouteUpdate方法在当前路由发生变化时会被调用,例如路由参数的变化等都可以在此方法中捕获到。beforeRouteLeave方法会在将要离开当前页面时被调用。还有一点需要特别注意,在beforeRouteEnter方法中 不能使用this来获取当前组件实例,因为在导航守卫确认通过前,新的组件还没有被创建。如果一定要在导航被确认时使用当前组件实例处理一些逻辑, 可以通过next参数注册回调方法,示例如下:

1
2
3
4
5
6
7
beforeRouteEnter(to, from, next) {
console.log(to, from, "前置守卫")
next( (w) => {
console.log(w); // w为当前组件实例
})
return true;
},

当前置守卫确认了此次跳转后,next参数注册的回调方法会被执行,并且会将当前组件的实例作为参数传入。在beforeRouteUpdatebeforeRouteLeave方法中可以直接使用this关键字来获取当前组件实例,无须额外的操作。

总结Vue Router导航跳转的全过程

下面来总结Vue Router导航跳转的全过程。

第1步:导航被触发,可以通过router-link组件触发,也可以通过$router.push或直接改变URL触发。

第2步:在将要失活的组件中调用beforeRouteLeave守卫函数。

第3步:调用全局注册的beforeEach守卫。

第4步:如果当前使用的组件没有变化,调用组件内的beforeRouteUpdate守卫。

第5步:调用在定义路由时配置的beforeEnter守卫函数。

第6步:解析异步路由组件。

第7步:在被激活的组件中调用beforeRouteEnter守卫。

第8步:导航被确认。

第9步:调用全局注册的afterEach守卫。

第10步:触发DOM更新,页面进行更新。

第11步:调用组件的beforeRouteEnter函数汇总next参数注册的回调函数。

动态路由

截至目前,我们所有使用到的路由都是采用静态配置的方式定义的,即先在main.js中完成路由的配置,再在项目中使用。但某些情况下,可能需要在运行的过程中动态添加或删除路由,VueRouter中也提供了方法支持动态地对路由进行操作。

动态添加与删除路由

在Vue Router中,动态操作路由的方法主要有两个: addRoute 和removeRoute, addRoute用来动态添加一条路由,对应的removeRoute用来动态删除一条路由。 首先,修改Demo1, Vue文件如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<template>
<div>
<h1>示例页面1</h1>
<el-button type="primary" @click="click">跳转Demo2</el-button>
</div>
</template>
<script>
import Demo2 from "./DemoTwo.vue";
export default {
created(){
console.log(this);
this.$router.addRoute({
path:"/demo2",
component: Demo2,
});
},
methods: {
click(){
this.$router.push("/demo2")
}
},
}
</script>

我们在Demo1组件中布局了一个按钮元素,在Demo1组件创建完成后,使用addRoute方法动态添加了一条路由,当单击页面上的按钮时,切换到Demo2组件。修改main.js文件中配置路由的部分如下:

1
2
3
4
5
6
7
8
9
10
import Demo1 from './components/DemoOne.vue'
// 定义路由
const routes = [
{path: '/demo1',component:Demo1},
]
// 创建路由对象
const router = createRouter({
history: createWebHashHistory(),
routes: routes // 我们定义的路由配置对象
})

可以尝试一下,如果直接在浏览器中访问/demo2页面会报错,因为此时注册的路由列表中没有此项路由记录,但是如果先访问/demo1页面,在单击页面上的按钮进行路由跳转,则能够正常跳转。

在下面几种场景下会触发路由的删除。

当使用addRoute方法动态添加路由时,如果添加了重名的路由,旧的会被删除,例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import Demo2 from "./DemoTwo.vue";
export default {
created(){
console.log(this);
this.$router.addRoute({
path:"/demo2",
component: Demo2,
name:"Demo2"
});
this.$router.addRoute({
path:"/d2",
component: Demo2,
name:"Demo2"
});
},
methods: {
click(){
this.$router.push("/demo2")
}
},
}

上面的代码中,路径为/demo的路由将会被删除。

在调用addRouter方法时,它其实会返回一个删除回调,我们也可以通过此删除回调来直接删除所添加的路由,代码如下:

1
2
3
4
5
6
7
let call = this.$router.addRoute({
path:"/demo2",
component: Demo2,
name:"Demo2"
});
// 直接移除此路由
call();

另外,对于已经命名的路由,也可以通过名称来对路由进行删除,示例如下:

1
2
3
4
5
6
this.$router.addRoute({
path:"/demo2",
component: Demo2,
name:"Demo2"
});
this.$router.removeRoute("Demo2")

需要注意:当路由被删除时,其所有的别名和子路由也会同步被删除,在Vue Router中,还提供了方法来获取现有的路由,例如:

1
2
console.log(this.$router.hasRoute("Demo2"));
console.log(this.$router.getRoutes());

其中,hasRoute方法用来检查当前已经注册的路由是否包含某个路由getRoutes方法用来获取包含所有路由的列表