☔️ Vue Router 路由
应用理解:要实现 SPA(Single Page Web Application) 单页面网页应用(整个应用只有一个完整的页面),在导航去点击导航链接切换不同的内容展示,但页面不会整体刷新,只会做页面的局部刷新,数据需要 AJAX 请求获取。路由器监测地址路径发生变化,按照路由规则引导到相应的内容。
路由与路由器的理解:
- 一个路由(Route)就是一组映射关系(
key-value
对应关系,key
是路径,value
是function
或者component
。) - 多个路由需要路由器(Router) 进行管理。
- 一个路由(Route)就是一组映射关系(
路由的分类:
前端路由:
value
为component
,用于展示页面内容。工作过程:当浏览器路径改变时,对应的组件就会显示在页面。 所以要使用路由的前提是,展示的不同内容区分为不同的组件。后端路由:
value
为function
,用于处理客户端提交的请求。工作过程:服务器收到一个请求时,根据请求路径找到匹配的函数来处理请求,返回响应数据。
# 路由的基本使用
- 安装
vue-router
:
$ npm i vue-router@3
2022 年 2 月 7 日以后, vue-router 的默认版本为 vue-router 4,并且 vue-router 4 只能在 Vue 3 下运行。所以,在基于 Vue 2 的学习中可以安装 vue-router 3。
- 编写路由文件
src/router/index.js
:
// 该文件专门用于创建整个应用的路由器
// 引入VueRouter
import VueRouter from "vue-router"
// 引入路由组件
// 创建并暴露一个路由器
export default new VueRouter({
routes: [
{
path: '',
component: ,
}
]
})
2
3
4
5
6
7
8
9
10
11
12
13
14
- 在入口文件
main.js
中引入router
,应用插件:
...
// 引入VueRouter
import VueRouter from "vue-router"
// 引入路由器
import router from './router'
...
// 应用插件
Vue.use(VueRouter)
new Vue({
el: '#app',
render: h => h(app),
router: router
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 简单的路由案例
- 在
App
组件中,实现导航栏的切换:使用router-link
标签,可以实现类似<a>
标签的页面跳转功能。
<div class="list-group">
<!--原始HTML中使用a标签实现页面的跳转-->
<!-- <a class="list-group-item" href="./about.html">About</a>
<a class="list-group-item active" href="./home.html">Home</a>-->
<!--Vue中借助router-link标签实现类似a标签的路由切换-->
<router-link class="list-group-item" active-class="active" to="/about">about</router-link>
<router-link class="list-group-item" active-class="active" to="/home">home</router-link>
</div>
2
3
4
5
6
7
8
样式同样使用
class
属性配置相应的样式,并且高亮样式可以使用active-class
配置。
- 在指定位置展示指定的路由组件内容:使用
<router-view>
标签。
<div class="panel-body">
<!--制定组件的呈现位置-->
<router-view></router-view>
</div>
2
3
4
# 路由使用的几个注意点
路由组件(通过路由展示页面的内容)通常存放在
pages
文件夹,一般组件(普通通过标签引入组件的内容)通常存放在components
文件夹。当切换路由组件时,「被隐藏」(被切走)的路由组件,默认是被销毁的,需要的时候才会去挂载。
两个路由组件的组件实例都有
$route
,但是里面的路由内容只与目前的路由组件相关;另外都有同一个$router
。
- 每个组件都有自己的
$route
属性,存储自己的路由信息。 - 整个应用只有一个
$router
,可以通过组件的$router
属性获取。
# 多级路由(嵌套路由)
# 嵌套路由案例
在 Home
组件中实现内容由不同的组件呈现,此时可以使用嵌套路由。
- 配置路由规则,使用
children
配置项配置嵌套路由:
routes: [
{
path: '/about',
component: About
},
{
path: '/home',
component: Home,
children: [
{
path: 'message',
component: Message
},
{
path: 'news',
component: News
},
]
}
]
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
注意
与一级路由的路径写法不同,在多级路由中的 path
路径配置项中,不必再在路径前面添加 /
,因为 children
下的配置默认为上一级路由的子级路由。
- 在组件中同样适用
router-link
完成路由跳转,注意to
属性的路径要写完整的路径,否则无法匹配。
<ul class="nav nav-tabs">
<li>
<router-link class="list-group-item" active-class="active" to="/home/news">News</router-link>
</li>
<li>
<router-link class="list-group-item" active-class="active" to="/home/message">Message</router-link>
</li>
</ul>
2
3
4
5
6
7
8
# 路由的更多配置
# 路由传参
# query
传参
通过 router-link
给目标组件传递参数时可以使用 query
传递。
🌰 使用 query
传递参数的例子:
- 传递参数:
<li v-for="message in messageList" :key="message.id">
<!--跳转路由并且携带query参数,to的字符串写法-->
<!--<router-link :to="`/home/message/detail?id=${message.id}&title=${message.title}`"> {{ message.title }}</router-link>-->
<!--跳转路由并且携带query参数,to的对象写法-->
<router-link :to="{
path: '/home/message/detail',
query: {
id: message.id,
title: message.title
}
}"> {{ message.title }}</router-link>
</li>
2
3
4
5
6
7
8
9
10
11
12
13
router-link
标签中的to
有两种写法,字符串形式(由于要传入参数所以要使用``
符号包裹 )过于繁琐可以使用对象形式(注意要使用对象形式,必须使字符串内为表达式,所以属性名为:to
),传递到目标组件的$route
中。
- 接收参数:
<ul>
<li>消息编号 {{ $route.query.id }}</li>
<li>消息标题 {{ $route.query.title }}</li>
</ul>
2
3
4
# params
传参
通过 router-link
传递参数的不同于 query
的写法(字符串形式)的另一种传递参数方法。使用方法:要现在路由配置文件中声明传递的参数在路径中的格式,然后才能在路由路径中匹配出对应的参数(或者直接使用对象指定名称传递)。一般直接用于简化字符串形式。
🌰:使用 params
传递参数的例子:
- 配置路由文件的
Detail
相关的路由:
...
{
path: 'message', // 子级路由不用斜杠
component: Message,
children: [
{
name: 'detail',
path: 'detail/:id/:title',
component: Detail
}
]
},
...
2
3
4
5
6
7
8
9
10
11
12
13
- 传递参数:
<li v-for="message in messageList" :key="message.id">
<!--跳转路由并且携带params参数,to的字符串写法-->
<router-link :to="`/home/message/detail/${message.id}/${message.title}`"> {{ message.title }}</router-link>
<!--跳转路由并且携带params参数,to的对象写法-->
<router-link :to="{
name: 'detail',
params:{
id: message.id,
title: message.title
}
}"> {{ message.title }}
</router-link>
</li>
2
3
4
5
6
7
8
9
10
11
12
13
14
使用
params
传递参数时,to
的对象写法一定要使用name
配置项,不能使用path
配置项。
- 接收参数:
<ul>
<li>消息编号 {{ $route.params.id }}</li>
<li>消息标题 {{ $route.params.title }}</li>
</ul>
2
3
4
# 命名路由
将路由语义化的配置 name
,可以简化跳转的路径,注意只能使用对象形式的 name
配置命名,不能用于简化形式(字符串),简化形式默认值给的是 path
配置。
🌰 :使用命名路由的例子:
- 在路由配置文件中,给相关的路由命名:
routes: [
{
name: 'about',
path: '/about',
component: About
},
{
name: 'home',
path: '/home',
component: Home,
}
]
2
3
4
5
6
7
8
9
10
11
12
- 在组件中使用
router-link
配置:to
属性时:
<router-link class="list-group-item" active-class="active" :to="{ name: 'About' }">about</router-link>
一般用于路由路径较长的情况。例如上述例子路由路径较短可以直接使用字符串形式的路由路径即可。
# props
配置
前面使用 params
或者 query
传递参数在接收参数时,都是直接在模版中使用 $route
直接读取。 props
配置可以让路由组件更加方便地接收到参数。
🌰 使用 props
传递参数时:
- 在路由配置文件中在要接收参数的组件路由配置
props
,有三种写法:
...
{
name: 'detail',
path: 'detail/:id/:title',
component: Detail,
// 1 props中值为对象,该对象中的所有key-value都会以props传给Detail组件中的$route.props
// props: {a: 1, b: hello}
// 2 值为布尔值,若布尔值为真就会把路由组件收到的所有params参数,以props的形式传送给Detail组件(只能适用params传参)
// props: true
// 3 props值为函数,该对象中的所有key-value都会以props传给Detail组件
props($route) {
return {
id: $route.params.id,
title: $route.params.title
}
}
/*props({query: id,title}) {
return { id, title }
}*/
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
- 传递参数时可以使用
query
或者params
配置。 - 接收参数时,先使用
props
接收,然后再在模版中简化使用。
props: ['id', 'title']
<ul>
<li>消息编号 {{ id }}</li>
<li>消息标题 {{ title }}</li>
</ul>
2
3
4
# replace
属性
作用:控制路由跳转时操作浏览器历史记录的模式。
浏览器的历史记录有两种写入方式:
push
:追加历史记录;replace
:替换当前记录,前面的记录不会受到影响。路由跳转时默认为
push
方式。
使用:在 <router-link>
中添加 replace
属性,修改浏览器的历史记录写入方式。
<router-link replace .......>News</router-link>
:replace=“true”
简写为replace
。
# 编程式路由导航
作用:不借助 <router-link>
实现路由的跳转,让路由跳转更加灵活。(当最终样式不为 <a>
标签,为其他的标签样式或者存在其他功能)主要使用 $router
中的 API。
🌰 使用 $router
实现 push
、 replace
、 back
、 forward
跳转的例子:
- 在组件中,跳转并且传递参数,替换掉之前使用的
router-link
标签。
methods: {
pushShow(message) {
this.$router.push({
name: 'detail',
params:{
id: message.id,
title: message.title
}
})
},
replaceShow(message) {
this.$router.replace({
name: 'detail',
params:{
id: message.id,
title: message.title
}
})
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
- 使用两个按钮实现页面的前进和后退,或者前进或者后退指定的步数。
methods: {
back() {
this.$router.back()
},
forward() {
this.$router.forward()
},
go(){
this.$router.go(-2) // 参数为步数
}
}
2
3
4
5
6
7
8
9
10
11
# 缓存路由组件
作用:让不展示的路由组件保持挂载,不被销毁。
使用:使用 <keep alive>
包裹要保留的路由视图。属性 include
指定要保留的组件名称。
<keep-alive include="News">
<router-view/>
</keep-alive>
2
3
<keep-alive :include="['News','Message']">
<router-view/>
</keep-alive>
2
3
# 路由生命周期钩子
- 作用:路由组件所独有的两个钩子,用于捕获路由组件的激活状态。
- 具体名字:
activated
路由组件被激活时触发。deactivated
路由组件失活时触发。
🌰 例子:
activated() {
console.log('News组件被激活')
this.timer = setInterval(() => {
// console.log('@')
this.opacity -= 0.01
if (this.opacity <= 0) this.opacity = 1
}, 16)
},
deactivated() {
console.log('News组件失活了')
clearInterval(this.timer)
}
2
3
4
5
6
7
8
9
10
11
12
# 路由守卫
- 作用:对路由进行权限控制。
- 分类:全局守卫、独享守卫、组件内守卫。
🌰 全局守卫的例子:
- 全局前置守卫:初始化执行、每次路由切换前执行:
router.beforeEach((to, from, next) => {
console.log('@', to, from)
if(to.meta.isAuth) {
if (localStorage.getItem('name') === 'simon') {
next()
} else {
alert('没有权限')
}
} else {
next()
}
})
2
3
4
5
6
7
8
9
10
11
12
- 在要使用权限判断的组件路由配置中添加新的配置
meta
存储相关的属性,如isAuth
,当isAuth
为true
时表示需要进行权限判断:
{
name: 'Message',
path: 'message',
component: Message,
meta: {isAuth: true, title:'消息'},
}
2
3
4
5
6
- 后置路由守卫:初始化执行、每次路由切换之后执行:
router.afterEach((to, from) => {
console.log('afterEach',to, from)
// 借助后置路由守卫修改网页标题,如果使用前置守卫编码重复
document.title = to.meta.title || 'Vue Guard DEMO'
})
2
3
4
5
注意:后置路由守卫不会有
next
。
🌰 独享守卫例子:
独享路由只有在切换目标路由的之前执行,即只能配置 beforeEnter
:
- 给
News
组件配置beforeEnter
:
{
name: 'News',
path: 'news',
component: News,
meta: {isAuth: true, title: '新闻'},
beforeEnter: (to, from, next) => {
if (to.meta.isAuth) {
if (localStorage.getItem('name') === 'simon') {
next()
} else {
alert('没有权限')
}
} else {
next()
}
}
},
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
🌰 组件内守卫:
- 是通过路由规则切换路由或者进入路由时触发的。
- 要与前置路由与后置路由区分,这是发生在前置路由之后,以及后置路由之前,在组件内部处理的守卫逻辑,并且不离开当前组件不会调用
beforeRouteLeave
。
// 通过路由规则,进入该组件时被调用
beforeRouteEnter(to, from, next) {
// ...
},
// 通过路由规则,离开该组件时被调用
beforeRouteLeave(to, from, next) {
// ...
}
2
3
4
5
6
7
8
# 路由器的两种工作模式
关于 Hash 值:
是 URL 地址从
#
开始以及其后面的内容。Hash 值不会包含在 HTTP 请求中,即 Hash 值不会做为路径的一部分发送给服务器。
路由器有两种工作模式,默认是 Hash 模式:
Hash 模式:
- URL 地址总是会带着
#
。 - 地址分享时可能会标记为不合法。
- 浏览器兼容性较好。
- URL 地址总是会带着
history 模式:
URL 地址与 Hash 模式相比不带有
#
。浏览器兼容性与 Hash 模式相比相比略差。
应用部署上线时需要后端人员支持,要解决刷新页面时服务端找不到资源的问题。
🔗 相关链接:connect-history-api-fallback - npm (npmjs.com) (opens new window) Middleware to proxy requests through a specified index page, useful for Single Page Applications that utilise the HTML5 History API.
🌰 修改路由器的工作模式的例子:
- 在路由器配置文件中,添加
mode
配置:
const router = new VueRouter({
mode: 'history',
routes: ...
})
2
3
4