vue-router3.X
vue-router
官方文档:https://v3.router.vuejs.org/zh/guide/
路由基础
概念
- 理解: 一个路由(route)就是一组映射关系(key - value),多个路由需要路由器(router)进行管理。
- 前端路由:key 是路径,value 是组件。
- 后端路由:value 是 function,用于处理请求路径找到匹配的函数来处理 (
@RequestMapping
)
vue-router3.x概述
Vue Router 是 Vue.js 的官网路由。用于构建 Single Page Application,包括以下特性:
- Nested 的 route/view mapping
- 现代化、组件化的路由配置
- 路由 params、query 和通配符
- 转场动画
- 细粒度的导航控制
- links 自动 active CSS
- HTML5 history mode or hash mode, with auto-fallback in IE9
- Customizable Scroll Behavior
vue-router 基本使用
HelloWorld
安装
安装 vue-router,命令:
1
npm i vue-router@3
vue-router 和 Vue 版本兼容问题
- vue2 用 vue-router3.x 版本
1
2
3
4
npm i vue-router@3
# 如果用的Vue2.x安装错了vue-router版本,先卸载,再安装vue-router3.x版本
npm uninstall vue-router
npm i vue-router@3
- vue3 用 vue-router4.x 版本
1
2
# 截止到2024年2月20日
npm i vue-router
编写 router 配置项和应用插件
src/router/index.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
31
32
33
34
35
import Vue from 'vue'
import VueRouter from "vue-router"
import AppTools from "../components/AppTools.vue"
import AppDeeplink from "../components/AppDeeplink.vue"
import AppOnelink from "../components/AppOnelink.vue"
import AppGPIR from "../components/AppGPIR.vue"
Vue.use(VueRouter)
const router = new VueRouter({
routes: [
{
path: "",
redirect: "/tools"
},
{
name: "tools",
path: "/tools",
component: AppTools
},
{
name: "deeplink",
path: "/deeplink",
component: AppDeeplink
},
{
name: "onelink",
path: "/onelink",
component: AppOnelink
}
],
mode: "history",
linkActiveClass: "active"
})
export default router;
在 main.js 引入
1
2
3
4
5
6
7
8
9
10
11
import Vue from 'vue'
// vue-router引入路由器
import router from './router'
import App from './App.vue'
// ...
new Vue({
el: '#app',
render: h => h(App),
router
}).$mount('#app')
router-view 标签指定展示位置
用 <router-view/>
标签,
1
<router-view></router-view>
实现切换(active-class 可配置高亮样式)
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
<template>
<div>
<div class="row">
<div class="col-xs-offset-2 col-xs-8">
<div class="page-header">
<h2>Vue Router Demo</h2>
</div>
</div>
</div>
<div class="row">
<div class="col-xs-2 col-xs-offset-2">
<div class="list-group">
<!-- 原始html中我们使用a标签实现页面的跳转 -->
<!-- <a class="list-group-item active" href="./about.html">About</a>
<a class="list-group-item" href="./home.html">Home</a> -->
<!-- Vue中借助router-link标签实现路由的切换 -->
<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>
</div>
<div class="col-xs-6">
<div class="panel">
<div class="panel-body">
<!-- 指定组件的呈现位置 -->
<router-view></router-view>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'App',
}
</script>
几个注意点
- 路由组件通常存放在
pages
文件夹,一般组件通常存放在components
文件夹。 - 通过切换,” 隐藏 “ 了的路由组件,默认是被销毁掉的,需要的时候再去挂载。
- 每个组件都有自己的
$route
属性,里面存储着自己的路由信息。 - 整个应用只有一个 router,可以通过组件的
$router
属性获取到。 - 在配置 routers 时,引入的 component 大小写编译器可能不报错,但是 vue 编译会报错。
基础
起步
HTML 方式 router-link标签
<router-link>
组件支持用户在具有路由功能的应用中 (点击) 导航。 通过 to
属性指定目标地址,默认渲染成带有正确链接的 <a>
标签,可以通过配置 tag
属性生成别的标签.。另外,当目标路由成功激活时,链接元素自动设置一个表示激活的 CSS 类名:.router-link-active
,可以通过 active-class="active"
自定义类名。
属性
to
类型: string Location - required
表示目标路由的链接。当被点击后,内部会立刻把 to 的值传到 router.push(),所以这个值可以是一个字符串或者是描述目标位置的对象。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<!-- 字符串 -->
<router-link to="home">Home</router-link>
<!-- 渲染结果 -->
<a href="home">Home</a>
<!-- 使用 v-bind 的 JS 表达式 -->
<router-link v-bind:to="'home'">Home</router-link>
<!-- 不写 v-bind 也可以,就像绑定别的属性一样 -->
<router-link :to="'home'">Home</router-link>
<!-- 同上 -->
<router-link :to="{ path: 'home' }">Home</router-link>
<!-- 命名的路由 -->
<router-link :to="{ name: 'user', params: { userId: 123 }}">User</router-link>
<!-- 带查询参数,下面的结果为 /register?plan=private -->
<router-link :to="{ path: 'register', query: { plan: 'private' }}"
>Register</router-link
>
replace
- 类型: boolean
- 默认值: false
设置 replace 属性的话,当点击时,会调用 router.replace() 而不是 router.push(),于是导航后不会留下 history 记录。
- 作用:控制路由跳转时操作浏览器历史记录的模式
- 浏览器的历史记录有两种写入方式:分别为
push
和replace
,push
是追加历史记录,replace
是替换当前记录。路由跳转时候默认为push
- 如何开启
replace
模式:<router-link replace …….>News</router-link>
1
<router-link :to="{ path: '/abc'}" replace></router-link>
append
- 类型: boolean
- 默认值: false
设置 append 属性后,则在当前 (相对) 路径前添加基路径。例如,我们从 /a 导航到一个相对路径 b,如果没有配置 append,则路径为 /b,如果配了,则为 /a/b
1
<router-link :to="{ path: 'relative/path'}" append></router-link>
tag
- 类型: string
- 默认值: “a”
有时候想要 <router-link>
渲染成某种标签,例如 <li>
。 于是我们使用 tag prop 类指定何种标签,同样它还是会监听点击,触发导航。
1
2
3
<router-link to="/foo" tag="li">foo</router-link>
<!-- 渲染结果 -->
<li>foo</li>
active-class
- 类型: string
- 默认值: “router-link-active”
设置链接激活时使用的 CSS 类名。默认值可以通过路由的构造选项 linkActiveClass
来全局配置
exact
- 类型: boolean
- 默认值: false
” 是否激活 “ 默认类名的依据是包含匹配。 举个例子,如果当前的路径是 /a
开头的,那么 <router-link to="/a">
也会被设置 CSS 类名。
v-slot API (3.1.0 新增)
在使用 v-slot
API 时,需要向 router-link 传入一个单独的子元素。否则 router-link 将会把子元素包裹在一个 span 元素内。
1
2
3
4
5
6
7
8
9
<router-link
to="/about"
custom
v-slot="{ href, route, navigate, isActive, isExactActive }"
>
<NavLink :active="isActive" :href="href" @click="navigate"
>
</NavLink>
</router-link>
- href:解析后的 URL。将会作为一个 a 元素的 href attribute。
- route:解析后的规范化的地址。
- navigate:触发导航的函数。会在必要时自动阻止事件,和 router-link 同理。
- isActive:如果需要应用 激活的 class 则为 true。允许应用一个任意的 class。
- isExactActive:如果需要应用 精确激活的 class 则为 true。允许应用一个任意的 class。
示例:将激活的 class 应用在外层元素
有的时候我们可能想把激活的 class 应用到一个外部元素而不是 <a>
标签本身,这时你可以在一个 router-link 中包裹该元素并使用 v-slot property 来创建链接:
1
2
3
4
5
6
7
8
9
10
11
<router-link
to="/foo"
v-slot="{ href, route, navigate, isActive, isExactActive }"
custom
>
<li
:class="[isActive && 'router-link-active', isExactActive && 'router-link-exact-active']"
>
<a :href="href" @click="navigate"></a>
</li>
</router-link>
如果你在
<a>
元素上添加一个 target=”_blank”,则 @click=”navigate” 处理器会被忽略。
示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<script src="https://unpkg.com/vue@2/dist/vue.js"></script>
<script src="https://unpkg.com/vue-router@3/dist/vue-router.js"></script>
<div id="app">
<h1>Hello App!</h1>
<p>
<!-- 使用 router-link 组件来导航. -->
<!-- 通过传入 `to` 属性指定链接. -->
<!-- <router-link> 默认会被渲染成一个 `<a>` 标签 -->
<router-link to="/foo">Go to Foo</router-link>
<router-link to="/bar">Go to Bar</router-link>
</p>
<!-- 路由出口 -->
<!-- 路由匹配到的组件将渲染在这里 -->
<router-view></router-view>
</div>
- 通过
router-link
组件来导航,to
属性指定跳转的链接;router-link 默认会渲染成<a>
标签 - 通过
router-view
组件匹配路由出口
当
<router-link>
对应的路由匹配成功,将自动设置 class 属性值router-link-active
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
31
// 0. 如果使用模块化机制编程,导入Vue和VueRouter,要调用 Vue.use(VueRouter)
// 1. 定义 (路由) 组件。
// 可以从其他文件 import 进来
const Foo = { template: '<div>foo</div>' }
const Bar = { template: '<div>bar</div>' }
// 2. 定义路由
// 每个路由应该映射一个组件。 其中"component" 可以是
// 通过 Vue.extend() 创建的组件构造器,
// 或者,只是一个组件配置对象。
// 我们晚点再讨论嵌套路由。
const routes = [
{ path: '/foo', component: Foo },
{ path: '/bar', component: Bar }
]
// 3. 创建 router 实例,然后传 `routes` 配置
// 你还可以传别的配置参数, 不过先这么简单着吧。
const router = new VueRouter({
routes // (缩写) 相当于 routes: routes
})
// 4. 创建和挂载根实例。
// 记得要通过 router 配置参数注入路由,
// 从而让整个应用都有路由功能
const app = new Vue({
router
}).$mount('#app')
// 现在,应用已经启动了!
- 访问路由
可以在任何组件内通过 this.$router
访问路由器,也可以通过 this.$route
访问当前路由:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Home.vue
export default {
computed: {
username() {
// 我们很快就会看到 `params` 是什么
return this.$route.params.username
}
},
methods: {
goBack() {
window.history.length > 1 ? this.$router.go(-1) : this.$router.push('/')
}
}
}
嵌套路由/多级路由
要在嵌套的出口中渲染组件,需要在 VueRouter 的参数中使用 children 配置:
- 配置路由规则,使用
children
配置项:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
routes:[
{
path:'/about',
component:About,
},
{
path:'/home',
component:Home,
children:[ //通过children配置子级路由
{
path:'news', //此处一定不要写:/news
component:News
},
{
path:'message',//此处一定不要写:/message
component:Message
}
]
}
]
要注意,以 / 开头的嵌套路径会被当作根路径。 这让你充分的使用嵌套组件而无须设置嵌套的路径。 children 配置就是像 routes 配置一样的路由配置数组,所以呢,你可以嵌套多层路由。
- 跳转(要写完整路径):
1
<router-link to="/home/news">News</router-link>
示例:子页面的切换
- Home.vue
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
<template>
<div>
<h2>Home组件内容</h2>
<div>
<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>
<router-view></router-view>
</div>
</div>
</template>
<script>
export default {
name: 'Home',
beforeDestroy() {
console.log('Home组件即将被销毁了')
},
mounted() {
console.log('Home组件挂载完毕了', this)
window.homeRoute = this.$route
window.homeRouter = this.$router
},
}
</script>
- 切换到 Home 的 News Tab
- 切换到 Home 的 Message Tab
编程式路由导航
作用:不借助 <router-link>
创建 a
标签来定义导航链接,让路由跳转更加灵活
router.push(location, onComplete?, onAbort?)
注意:在 Vue 实例内部,你可以通过 $router 访问路由实例。因此你可以调用
this.$router.push
想要导航到不同的 URL,则使用 this.$router.push
方法。这个方法会向 history 栈添加一个新的记录,所以,当用户点击浏览器后退按钮时,则回到之前的 URL。
当你点击 <router-link>
时,这个方法会在内部调用,所以说,点击 <router-link :to="…">
等同于调用 this.$router.push(…)
。
声明式 | 编程式 |
---|---|
<router-link :to="…"> | this.$router.push(…) |
该方法的参数可以是一个字符串路径,或者一个描述地址的对象。例如:
1
2
3
4
5
6
7
8
9
10
11
// 字符串
this.$router.push('home')
// 对象
this.$router.push({ path: 'home' })
// 命名的路由
this.$router.push({ name: 'user', params: { userId: '123' }})
// 带查询参数,变成 /register?plan=private
this.$router.push({ path: 'register', query: { plan: 'private' }})
注意:如果提供了 path,params 会被忽略(也就是说要用 params,不能用 path,需要用 name),上述例子中的 query 并不属于这种情况。取而代之的是下面例子的做法,你需要提供路由的 name 或手写完整的带有参数的 path:
1
2
3
4
5
const userId = '123'
this.$router.push({ name: 'user', params: { userId }}) // -> /user/123
this.$router.push({ path: `/user/${userId}` }) // -> /user/123
// 这里的 params 不生效
this.$router.push({ path: '/user', params: { userId }}) // -> /user
同样的规则也适用于 router-link 组件的 to 属性。
在 2.2.0+,可选的在 router.push 或 router.replace 中提供 onComplete 和 onAbort 回调作为第二个和第三个参数。这些回调将会在导航成功完成 (在所有的异步钩子被解析之后) 或终止 (导航到相同的路由、或在当前导航完成之前导航到另一个不同的路由) 的时候进行相应的调用。
在 3.1.0+,可以省略第二个和第三个参数,此时如果支持 Promise,router.push 或 router.replace 将返回一个 Promise。
注意: 如果目的地和当前路由相同,只有参数发生了改变 (比如从一个用户资料到另一个 /users/1 -> /users/2),你需要使用 beforeRouteUpdate
来响应这个变化 (比如抓取用户信息)。
router.replace(location, onComplete?, onAbort?)
跟 router.push 很像,唯一的不同就是,它不会向 history 添加新记录,而是跟它的方法名一样 —— 替换掉当前的 history 记录。
声明式 | 编程式 |
---|---|
<router-link :to=”…” replace> | router.replace(…) |
router.go(n)
这个方法的参数是一个整数,意思是在 history 记录中向前或者后退多少步,类似 window.history.go(n)
。
1
2
3
4
5
6
7
8
9
10
11
12
// 在浏览器记录中前进一步,等同于 history.forward()
this.$router.go(1)
// 后退一步记录,等同于 history.back()
this.$router.go(-1)
// 前进 3 步记录
this.$router.go(3)
// 如果 history 记录不够用,那就默默地失败呗
this.$router.go(-100)
this.$router.go(100)
操作 History
1
2
3
this.$router.forward() //前进
this.$router.back() //后退
this.$router.go() //可前进也可后退(传递数字,根据正负前进和后退)
路由组件传参
query
- 传递参数方式 1:带在路由路径的 query
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
<template>
<div>
<ul>
<li v-for="m in messageList" :key="m.id">
<!-- 跳转路由并携带query参数,to的字符串写法 -->
<router-link :to="`/home/message/detail?id=${m.id}&title=${m.title}`"></router-link>
</li>
</ul>
<hr>
<router-view></router-view>
</div>
</template>
<script>
export default {
name: 'Message',
data() {
return {
messageList: [
{ id: '001', title: '消息001' },
{ id: '002', title: '消息002' },
{ id: '003', title: '消息003' }
]
}
},
}
</script>
用模板字符串 ```` 包裹
- 传递方式 2:对象方式
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
<template>
<div>
<ul>
<li v-for="m in messageList" :key="m.id">
<!-- 跳转路由并携带query参数,to的对象写法 -->
<router-link :to="{
path: '/home/message/detail',
query: {
id: m.id,
title: m.title
}
}">
</router-link>
</li>
</ul>
<hr>
<router-view></router-view>
</div>
</template>
<script>
export default {
name: 'Message',
data() {
return {
messageList: [
{ id: '001', title: '消息001' },
{ id: '002', title: '消息002' },
{ id: '003', title: '消息003' }
]
}
},
}
</script>
- 接收参数
1
2
$route.query.id
$route.query.title
params
- 路由定义
其中 Message 组件定义了 params 参数 detail/:id/:title
,不加 :id :title
占位会被当成 path 处理
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
// 该文件专门用于创建整个应用的路由器
import VueRouter from 'vue-router'
//引入组件
import About from '../pages/About'
import Home from '../pages/Home'
import News from '../pages/News'
import Message from '../pages/Message'
import Detail from '../pages/Detail'
//创建并暴露一个路由器
export default new VueRouter({
routes: [
{
name: 'guanyu',
path: '/about',
component: About
},
{
path: '/home',
component: Home,
children: [
{
path: 'news',
component: News,
},
{
path: 'message',
component: Message,
children: [
{
name: 'xiangqing',
path: 'detail/:id/:title',
component: Detail,
}
]
}
]
}
]
})
如何传递:
- 第一种:直接在路由后面拼接
- 第二种:通过对象的 params 传递
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
<template>
<div>
<ul>
<li v-for="m in messageList" :key="m.id">
<!-- 跳转路由并携带params参数,to的字符串写法 -->
<!-- <router-link :to="`/home/message/detail/${m.id}/${m.title}`"></router-link> -->
<!-- 跳转路由并携带params参数,to的对象写法,不能用path,只能用name -->
<router-link :to="{
name: 'xiangqing',
params: {
id: m.id,
title: m.title
}
}">
</router-link>
</li>
</ul>
<hr>
<router-view></router-view>
</div>
</template>
<script>
export default {
name: 'Message',
data() {
return {
messageList: [
{ id: '001', title: '消息001' },
{ id: '002', title: '消息002' },
{ id: '003', title: '消息003' }
]
}
},
}
</script>
特别注意:路由携带 params 参数时,若使用 to 的对象写法,则不能使用 path 配置项,必须使用 name 配置!
- 接收 params 参数
1
2
$route.params.id
$route.params.title
路由的 props 配置
作用:让路由组件更方便的收到参数
请尽可能保持 props 函数为无状态的,因为它只会在路由发生变化时起作用。如果你需要状态来定义 props,请使用包装组件,这样 Vue 才可以对状态变化做出反应。
布尔模式:取代与 $route
的耦合
如果 props 被设置为 true,route.params 将会被设置为组件属性。
示例:
路由配置:
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
//创建并暴露一个路由器
export default new VueRouter({
routes:[
{
name:'guanyu',
path:'/about',
component:About
},
{
path:'/home',
component:Home,
children:[
{
path:'news',
component:News,
},
{
path:'message',
component:Message,
children:[
{
name:'xiangqing',
path:'detail',
component:Detail,
// props的第二种写法,值为布尔值,若布尔值为真,就会把该路由组件收到的所有params参数,以props的形式传给Detail组件。
props:true
}
]
}
]
}
]
})
通过 props 传递:
1
2
3
4
5
6
7
8
9
10
<!-- 跳转路由并携带params参数,to的对象写法 -->
<router-link :to="{
name:'xiangqing',
query:{
id:m.id,
title:m.title
}
}">
</router-link>
接收组件 Detail.vue,可以用 prop 来接收,不用 this.$route.params
接收
1
2
3
4
5
6
7
8
9
10
11
12
13
<template>
<ul>
<li>消息编号:</li>
<li>消息标题:</li>
</ul>
</template>
<script>
export default {
name:'Detail',
props:['id','title']
}
</script>
对象模式
如果 props 是一个对象,它会被按原样设置为组件属性。当 props 是静态的时候有用。
案例:
该对象中的所有 key-value 都会以 props 的形式传给 Detail 组件
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
// 该文件专门用于创建整个应用的路由器
import VueRouter from 'vue-router'
//引入组件
import About from '../pages/About'
import Home from '../pages/Home'
import News from '../pages/News'
import Message from '../pages/Message'
import Detail from '../pages/Detail'
//创建并暴露一个路由器
export default new VueRouter({
routes:[
{
name:'guanyu',
path:'/about',
component:About
},
{
path:'/home',
component:Home,
children:[
{
path:'news',
component:News,
},
{
path:'message',
component:Message,
children:[
{
name:'xiangqing',
path:'detail',
component:Detail,
//props的第一种写法,值为对象,该对象中的所有key-value都会以props的形式传给Detail组件。
props:{a:1,b:'hello'}
}
]
}
]
}
]
})
- 接收的组件 Detail.vue
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<template>
<ul>
<li>消息编号:</li>
<li>消息标题:</li>
</ul>
</template>
<script>
export default {
name:'Detail',
computed: {
id(){
return this.$route.query.id
},
title(){
return this.$route.query.title
},
},
}
</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
41
42
43
44
45
46
47
48
49
50
51
// 该文件专门用于创建整个应用的路由器
import VueRouter from 'vue-router'
//引入组件
import About from '../pages/About'
import Home from '../pages/Home'
import News from '../pages/News'
import Message from '../pages/Message'
import Detail from '../pages/Detail'
//创建并暴露一个路由器
export default new VueRouter({
routes: [
{
name: 'guanyu',
path: '/about',
component: About
},
{
path: '/home',
component: Home,
children: [
{
path: 'news',
component: News,
},
{
path: 'message',
component: Message,
children: [
{
name: 'xiangqing',
path: 'detail',
component: Detail,
// props的第三种写法,值为函数
props($route) {
return {
id: $route.query.id,
title: $route.query.title,
a: 1,
b: 'hello',
id2: $route.params.id2,
}
}
}
]
}
]
}
]
})
props 是一个函数,参数为 $route
;返回的是一个对象,可以在 Detail.vue 组件通过 props 接收:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<template>
<ul>
<li>--消息编号:</li>
<li>==消息标题:</li>
<li>a=</li>
<li></li>
</ul>
</template>
<script>
export default {
name: 'Detail',
props: ['id', 'title', 'a', 'id2'],
mounted() {
console.log(this.$route)
console.log(this.$route.query)
console.log(this.$route.params)
},
}
</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
<template>
<div>
<ul>
<li v-for="m in messageList" :key="m.id">
<!-- 跳转路由并携带params参数,to的对象写法 -->
<router-link :to="{
name: 'xiangqing',
query: {
id: m.id,
title: m.title
},
params: {
id2: 'id2',
title2: 'title2'
}
}">
-
</router-link>
</li>
</ul>
<hr>
<router-view></router-view>
</div>
</template>
<script>
export default {
name: 'Message',
data() {
return {
messageList: [
{ id: '001', title: '消息001' },
{ id: '002', title: '消息002' },
{ id: '003', title: '消息003' }
]
}
},
}
</script>
命名路由
简化路由的跳转。
- 在创建 Router 实例的时候,在 routes 配置中给某个路由设置名称。
1
2
3
4
5
6
7
8
9
const router = new VueRouter({
routes: [
{
path: '/user/:userId',
name: 'user',
component: User
}
]
})
- 要链接到一个命名路由,可以给
router-link
的to
属性传一个对象,对象里的 name 配置路由的 name 名称:
1
2
3
4
<router-link :to="{
name: 'user',
params: { userId: 123 }
}">User</router-link>
注意是用的
:to
- 代码 push 也是一样
1
router.push({ name: 'user', params: { userId: 123 } })
命名视图
重定向和别名
缓存路由组件
作用:让不展示的路由组件保持挂载,不被销毁。
1
2
3
<keep-alive include="News">
<router-view></router-view>
</keep-alive>
这段代码的作用是:当页面切换到名称为 “News” 的组件时,它会被缓存起来,当用户再次浏览到该组件时,直接从缓存中读取,提高了页面的响应速度和用户体验。
<keep-alive>
是一个抽象组件,会将其包裹的内容存储在内存中,并在需要时缓存或销毁它们。include="News"
表示只有名称为 “News” 的组件才应该被缓存。如果不指定include
属性,则所有组件都将被缓存。<router-view>
用于渲染当前路由匹配到的组件。
如果要缓存多个组件,可以在 <keep-alive>
的 include
属性中指定一个数组来包含多个组件的名称。例如:
1
2
3
<keep-alive :include="['News', 'Article', 'Comment']">
<router-view></router-view>
</keep-alive>
在这个例子中,会将名为 “News”、”Article” 和 “Comment” 的三个组件都缓存起来。如果需要缓存更多的组件,只需要将它们的名称放入数组即可。
注意:当使用数组形式进行多个组件的缓存时,Vue.js 会根据它们在数组中的顺序依次匹配,如果找到匹配的组件,则会缓存它并停止继续匹配,因此,组件的顺序是有影响的,需要根据实际需求进行调整。
HTML5 History 模式
` Vue Router ` 有两种工作模式:hash
模式和 history
模式。默认 hash
hash 模式
在 hash
模式中,URL 中的路径部分以 #
开头,并且后面紧跟着一个由路由器管理的字符串,hash 值不会包含在 HTTP 请求中。例如,下面的 URL 表示访问 /home
路径:
1
http://localhost:8080/#/home
通过 hash
模式可以实现单页应用程序(SPA)的核心功能:在网页内部跳转而不需要刷新整个页面。当用户点击链接或者触发事件时,Vue Router 会解析 URL 中的 hash
部分,然后根据匹配的路由规则进行组件的渲染和显示。
history 模式
在 history
模式中,URL 中的路径部分不再使用 #
符号,而是直接使用正常的路径。例如,下面的 URL 表示访问 /home
路径:
1
http://localhost:8080/home
通过 history
模式可以实现更加友好的 URL,同时也可以在浏览器历史记录中记录用户浏览的页面,从而使用户可以使用 “ 前进 “、” 后退 “ 按钮进行导航。
要使用 history
模式,需要在创建 Vue Router 实例时配置 mode: 'history'
,如下所示:
1
2
3
4
5
6
const router = new VueRouter({
mode: 'history',
routes: [
// ...
]
})
需要注意的是,在使用 history
模式时,需要后端服务器进行配置,以保证在刷新页面时能够正确地返回对应的页面。否则,可能会出现 404 错误或者其他问题。
对于 Node.js/Express,请考虑使用 connect-history-api-fallback 中间件 解决访问 404 问题
两种模式比较
- 对于一个 url 来说,什么是 hash 值?——
#及其后面的内容
就是 hash 值。 - hash 值不会包含在 HTTP 请求中,即:hash 值不会带给服务器。
- hash 模式:
- 地址中永远带着
#
号,不美观 。 - 若以后将地址通过第三方手机 app 分享,若 app 校验严格,则地址会被标记为不合法。
- 兼容性较好。
- 地址中永远带着
- history 模式:
- 地址干净,美观 。
- 兼容性和 hash 模式相比略差。
- 应用部署上线时需要后端人员支持,解决刷新页面服务端 404 的问题。
进阶
路由守卫
什么是路由守卫?
vue-router 提供的导航守卫主要用来通过跳转或取消的方式守卫导航。有多种机会植入路由导航过程中:全局的, 单个路由独享的, 或者组件级的。
通过使用路由守卫,我们可以实现以下功能:
- 权限验证:在用户访问某些页面时,需要判断用户是否有访问权限。如果用户没有权限访问该页面,可以通过路由守卫拦截路由跳转,并弹出提示信息。
- 记录浏览历史:在用户浏览网站时,需要记录用户的浏览历史。通过路由守卫,在每次路由变化时记录浏览历史。
- 异步组件处理:在使用异步组件时,需要在组件加载完成之前显示一些占位信息。通过路由守卫,在异步组件加载完成之前显示占位信息。
- 路由重定向:在用户访问某个路径时,需要将用户重定向到其他路径。通过路由守卫,可以在路由跳转之前进行重定向操作。
全局前置守卫 beforeEach
1
2
3
4
5
const router = new VueRouter({ ... })
router.beforeEach((to, from, next) => {
// ...
})
当一个导航触发时,全局前置守卫按照创建顺序调用。守卫是异步解析执行,此时导航在所有守卫 resolve 完之前一直处于 等待中。
每个守卫方法接收三个参数:
- to: Route: 即将要进入的目标 路由对象
- from: Route: 当前导航正要离开的路由
- next: Function: 一定要调用该方法来 resolve 这个钩子。执行效果依赖 next 方法的调用参数。
- next(): 进行管道中的下一个钩子。如果全部钩子执行完了,则导航的状态就是 confirmed (确认的)。
- next(false): 中断当前的导航。如果浏览器的 URL 改变了 (可能是用户手动或者浏览器后退按钮),那么 URL 地址会重置到 from 路由对应的地址。
- next(‘/’) 或者 next({ path: ‘/’ }): 跳转到一个不同的地址。当前的导航被中断,然后进行一个新的导航。你可以向 next 传递任意位置对象,且允许设置诸如 replace: true、name: ‘home’ 之类的选项以及任何用在 router-link的toprop 或 router.push 中的选项。
- next(error): (2.4.0+) 如果传入 next 的参数是一个 Error 实例,则导航会被终止且该错误会被传递给 router.onError() 注册过的回调。
确保 next
函数在任何给定的导航守卫中都被严格调用一次。它可以出现多于一次,但是只能在所有的逻辑路径都不重叠的情况下,否则钩子永远都不会被解析或报错。这里有一个在用户未能验证身份时重定向到 /login 的示例:
1
2
3
4
5
// GOOD
router.beforeEach((to, from, next) => {
if (to.name !== 'Login' && !isAuthenticated) next({ name: 'Login' })
else next()
})
全局解析守卫 beforeResolve
在 2.5.0+ 你可以用 router.beforeResolve
注册一个全局守卫。这和 router.beforeEach
类似,区别是在导航被确认之前,同时在所有组件内守卫和异步路由组件被解析之后,解析守卫就被调用。
全局后置钩子 afterEach
你也可以注册全局后置钩子,然而和守卫不同的是,这些钩子不会接受 next 函数也不会改变导航本身:
1
2
3
router.afterEach((to, from) => {
// ...
})
路由独享的守卫 beforeEnter
可以在路由配置上直接定义 beforeEnter 守卫:
1
2
3
4
5
6
7
8
9
10
11
const router = new VueRouter({
routes: [
{
path: '/foo',
component: Foo,
beforeEnter: (to, from, next) => {
// ...
}
}
]
})
这些守卫与 全局前置守卫
的方法参数是一样的。
组件内的守卫
可以在路由组件内直接定义以下路由导航守卫:
- beforeRouteEnter
- beforeRouteUpdate (2.2 新增)
- beforeRouteLeave
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const Foo = {
template: `...`,
beforeRouteEnter(to, from, next) {
// 在渲染该组件的对应路由被 confirm 前调用
// 不!能!获取组件实例 `this`
// 因为当守卫执行前,组件实例还没被创建
},
beforeRouteUpdate(to, from, next) {
// 在当前路由改变,但是该组件被复用时调用
// 举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候,
// 由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
// 可以访问组件实例 `this`
},
beforeRouteLeave(to, from, next) {
// 导航离开该组件的对应路由时调用
// 可以访问组件实例 `this`
}
}
beforeRouteEnter 守卫 不能 访问 this,因为守卫在导航确认前被调用,因此即将登场的新组件还没被创建。
注意 beforeRouteEnter 是支持给 next 传递回调的唯一守卫。对于 beforeRouteUpdate 和 beforeRouteLeave 来说,this 已经可用了,所以不支持传递回调,因为没有必要了。
这个离开守卫通常用来禁止用户在还未保存修改前突然离开。该导航可以通过 next(false) 来取消。
1
2
3
4
5
6
7
8
beforeRouteLeave (to, from, next) {
const answer = window.confirm('Do you really want to leave? you have unsaved changes!')
if (answer) {
next()
} else {
next(false)
}
}
onError
当导航过程中出现未捕获的错误时调用。需要注意的是,如果在一个路由守卫中抛出了一个错误,此错误将会被传递到最后一个激活的全局错误处理程序。
完整的导航解析流程
- 导航被触发。
- 在失活的组件里调用 beforeRouteLeave 守卫。
- 调用全局的 beforeEach 守卫。
- 在重用的组件里调用 beforeRouteUpdate 守卫 (2.2+)。
- 在路由配置里调用 beforeEnter。
- 解析异步路由组件。
- 在被激活的组件里调用 beforeRouteEnter。
- 调用全局的 beforeResolve 守卫 (2.5+)。
- 导航被确认。
- 调用全局的 afterEach 钩子。
- 触发 DOM 更新。
- 调用 beforeRouteEnter 守卫中传给 next 的回调函数,创建好的组件实例会作为回调函数的参数传入。
路由元信息 meta
定义路由的时候可以配置 meta 字段:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const router = new VueRouter({
routes: [
{
path: '/foo',
component: Foo,
children: [
{
path: 'bar',
component: Bar,
// a meta field
meta: { requiresAuth: true }
}
]
}
]
})
下面例子展示在全局导航守卫中检查元字段:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
router.beforeEach((to, from, next) => {
if (to.matched.some(record => record.meta.requiresAuth)) {
// this route requires auth, check if logged in
// if not, redirect to login page.
if (!auth.loggedIn()) {
next({
path: '/login',
query: { redirect: to.fullPath }
})
} else {
next()
}
} else {
next() // 确保一定要调用 next()
}
})
数据获取
有时候,进入某个路由后,需要从服务器获取数据。例如,在渲染用户信息时,你需要从服务器获取用户的数据。我们可以通过两种方式来实现:
- 导航完成之后获取:先完成导航,然后在接下来的组件生命周期钩子中获取数据。在数据获取期间显示 “ 加载中 “ 之类的指示。
- 导航完成之前获取:导航完成前,在路由进入的守卫中获取数据,在数据获取成功后执行导航。
数据存储位置
存储在 Vuex 中
适用于需要共享变量的情况,可以让不同组件之间共享变量,并且可以在全局守卫、独享守卫和组件内守卫中进行访问。由于需要安装和配置 Vuex,因此相对麻烦一些。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 首先,在 Vuex 中定义一个状态
const state = {
isAuthenticated: false // 是否已经登录
}
...
// 在需要进行变量判断的地方,通过 mutations 修改状态
this.$store.commit('setAuthenticated', true);
...
// 在路由守卫中访问状态
router.beforeEach((to, from, next) => {
if (to.meta.requiresAuth && !store.state.isAuthenticated) {
next('/login');
} else {
next();
}
});
存储在当前组件数据中
适用于只需要在当前组件进行变量判断的情况,可以在组件内部直接进行访问和修改,但是无法在其他组件和路由中共享变量。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 在组件的 data 选项中定义一个变量
data() {
return {
isAuthenticated: false // 是否已经登录
}
},
// ...
// 在需要进行变量判断的地方,修改变量的值
this.isAuthenticated = true;
// ...
// 在组件内守卫中访问变量
beforeRouteEnter(to, from, next) {
if (!this.isAuthenticated) {
next('/login');
} else {
next();
}
}
存储在路由中 meta
适用于只需要在当前路由进行变量判断的情况,可以在独享守卫和组件内守卫中进行访问。但是会导致路由配置变得臃肿,不易于维护。
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
// 在路由配置中定义一个变量
const routes = [
{
path: '/home',
component: Home,
meta: {
requiresAuth: true // 是否需要登录权限
}
},
// ...
]
// ...
// 在需要进行变量判断的地方,通过 meta 属性修改变量的值
this.$router.push({
path: '/home',
meta: {
requiresAuth: true,
isAuthenticated: true
}
})
// 在独享守卫和组件内守卫中访问变量
beforeEnter(to, from, next) {
if (!to.meta.isAuthenticated) {
next('/login');
} else {
next();
}
}
beforeRouteEnter(to, from, next) {
if (!to.meta.isAuthenticated) {
next('/login');
} else {
next(vm => {
vm.isAuthenticated = to.meta.isAuthenticated;
});
}
}
需要注意的是,在进行路由跳转之前,需要根据变量的值决定是否进行路由跳转,并且在组件内守卫中访问变量时,需要使用 next
方法的回调函数来更新组件的数据。另外,将变量存储在路由中会导致路由配置变得臃肿,不易于维护,因此建议在需要共享变量的情况下使用 Vuex 状态管理中心。