Vue路由
# 单Web应用(SPA)
SPA(Single Page Web Application)是指整个web应用只有一个页面驱动。主要特点就是点击页面中的导航链接不会刷新页面,只会做页面的局部更新,数据一般通过ajax请求从后端获取。
# 路由
路由是一个比较广义和抽象的概念,其本质是对应关系的映射。key是路径,value可以是组件也可以是函数。
在开发中,路由主要分为两类:
后端路由
后端路由是根据不同的用户URL请求,返回不同的内容,其本质是URL请求地址和服务器资源之间的对应关系。
前端路由
前端路由是根据不同的用户事件,显示不同的页面内容,其本质是用户事件与处理函数之间的对应关系。
Vue Router (opens new window)是Vue.js官方的路由管理器。它和Vue.js的核心深度集成,可以非常方便的用于SPA应用程序开发。
# 基本使用
# 安装Vue Router
npm i vue-router
# 在main.js
内引入路由
Vue.use(VueRouter)
//引入Vue
import Vue from 'vue'
//引入App
import App from './App.vue'
//引入VueRouter
import VueRouter from 'vue-router'
//引入路由器
import router from './router'
//关闭Vue的生产提示
Vue.config.productionTip = false
//应用插件
Vue.use(VueRouter)
//创建vm
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
17
18
19
20
21
# 编写router配置项
创建router文件夹和index.js,在内部编写
//引入VueRouter
import VueRouter from 'vue-router'
//引入路由组件
import About from '../components/About'
import Home from '../components/Home'
//创建router实例对象,去管理一组一组的路由规则
const router = new VueRouter({
routes:[
{
path:'/about',
component:About
},
{
path:'/home',
component:Home
}
]
})
//暴露router
export default router
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 使用内置标签<router-link>
来实现路由切换。
<router-link active-class="active" to="/about">About</router-link>
router-link
最终会被浏览器解释为a
标签。active-class
可配置高亮样式。
<router-link>
还有一个replace
属性,它用于控制路由跳转时操作浏览器历史记录的模式,它的取值分别为push
和replace
,push
是追加历史记录,replace
是替换当前记录。路由跳转时候默认为push
。
# 使用内置标签<router-view>
来指定展示位置。
<router-view></router-view>
# Example
<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>
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
# 几个注意四点
- 路由组件通常存放在
pages
文件夹,一般组件通常存放在components
文件夹。 - 通过切换,“隐藏”了的路由组件,默认是被销毁掉的,需要的时候再去挂载。
- 每个组件都有自己的
$route
属性,里面存储着自己的路由信息。 - 整个应用只有一个router,可以通过组件的
$router
属性获取到。
# 重定向路由
重定向路由是指用户在访问地址A的时候,强制用户跳转到地址C,从而展示特定的组件页面,比如访问首页/
跳转到特定的/user
页面。
通过路由规则的redirect属性;指定一个新的路由地址,可以很方便地设置路由的重定向。
//实例化路由对象
const router = new VueRouter({
//routes 是路由规则数组
routes:[
//path 表示当前路由规则匹配的hash地址
//redirect 表示将要重定向的新地址
{path:'/',redirect:'/foo'},
{path:'/foo',component:Foo},
{path:'/bar',component:Bar}
]
})
2
3
4
5
6
7
8
9
10
11
# 嵌套路由
嵌套路由即当点击父路由链接时显示模板内容,在模板内容中又有子路由链接,点击子路由链接显示子级模板内容。
# 父路由组件模板
定义父路由链接"/user"和"/register",然后定义父组件填充位。
<div id="app">
<p>
<router-link to="/user">User</router-link>
<router-link to="/register">register</router-link>
<div>
<router-view></router-view>
</div>
</p>
</div>
<script type="text/javascript">
const User = {
template:`
<div>
<h1>User组件</h1>
</div>
`
}
const Register = {
template:`
<div>
<h1>Register组件</h1>
</div>
`
}
const router = new VueRouter({
routes:[
{path:'/user',component:User},
{path:'/register',component:Register}
]
})
const vm = new Vue({
el:"#app",
router
})
</script>
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
# 子组件模板
在父路由组件中添加子路由链接和子路由填充位。
const Register = {
template:`
<div>
<h1>Register组件</h1>
<!--子组件路由链接 (要写完整路径)-->
<router-link to="/register/tab1">tab1</router-link>
<router-link to="/register/tab2">tab2</router-link>
!--子组件填充位-->
<router-view></router-view>
</div>
`
}
//子组件1
const Tab1 = {
template:`
<div>
<h2>Tab1组件</h2>
</div>
`
}
//子组件2
const Tab2 = {
template:`
<div>
<h2>Tab2组件</h2>
</div>
`
}
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
在父级路由配置中通过children
属性配置子路由规则。
const router = new VueRouter({
routes:[
{path:'/user',component:User},
{
path:'/register',
component:Register,
//通过children属性,为/register添加子路由规则
children:[
{path:'/register/tab1',component:Tab1},
{path:'/register/tab2',component:Tab2}
]
}
]
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 路由参数
我们跳转路由的时候如果需要携带参数,那么就需要使用query或者param参数来实现路由参数。
# query参数
传递参数
<!-- 跳转并携带query参数,to的字符串写法 --> <router-link :to="/home/message/detail?id=666&title=你好">跳转</router-link> <!-- 跳转并携带query参数,to的对象写法 --> <router-link :to="{ path:'/home/message/detail', query:{ id:666, title:'你好' } }" >跳转</router-link>
1
2
3
4
5
6
7
8
9
10
11
12
13接收参数:
$route.query.id $route.query.title
1
2
# param参数
配置路由,声明接收params参数
{ path:'/home', component:Home, children:[ { path:'news', component:News }, { component:Message, children:[ { name:'xiangqing', path:'detail/:id/:title', //使用占位符声明接收params参数 component:Detail } ] } ] }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20传递参数
<!-- 跳转并携带params参数,to的字符串写法 --> <router-link :to="/home/message/detail/666/你好">跳转</router-link> <!-- 跳转并携带params参数,to的对象写法 --> <router-link :to="{ name:'xiangqing', params:{ id:666, title:'你好' } }" >跳转</router-link>
1
2
3
4
5
6
7
8
9
10
11
12
13特别注意:路由携带params参数时,若使用to的对象写法,则不能使用path配置项,必须使用name配置!
# 路由的props配置
props配置可以让路由组件更方便的接收参数,之前的$route
与对应路由形成高度耦合,不够灵活,所以可以使用props将组件和路由解耦。
<div id="app">
<p>
<router-link to="/user/1">User1</router-link>
<router-link to="/user/2">User2</router-link>
<router-link to="/user/3">User3</router-link>
<div>
<router-view></router-view>
</div>
</p>
</div>
<script type="text/javascript">
const User = {
//路由组件中通过$route.params获取路由参数
template:`
<div>User {{$route.params.id}}</div>
`
}
const router = new VueRouter({
routes:[
//动态路径参数,以冒号开头
{path:'/user/:id',component:User}
]
})
const vm = new Vue({
el:"#app",
router
})
</script>
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
# props值为布尔类型
props值为布尔值,布尔值为true,则把路由收到的所有params参数通过props传给组件。
<div id="app">
<p>
<router-link to="/user/1">User1</router-link>
<router-link to="/user/2">User2</router-link>
<router-link to="/user/3">User3</router-link>
<div>
<router-view></router-view>
</div>
</p>
</div>
<script type="text/javascript">
const User = {
props:['id'], //使用props接收路由参数
template:`
<div>User {{id}}</div>
`
}
const router = new VueRouter({
routes:[
//如果props被设置为true,route.params将会被设置为组件属性
{path:'/user/:id',component:User,props:true}
]
})
const vm = new Vue({
el:"#app",
router
})
</script>
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
# props值为对象类型
props值为对象,该对象中所有的key-value的组合最终都会通过props传给组件
<div id="app">
<p>
<router-link to="/user/1">User1</router-link>
<router-link to="/user/2">User2</router-link>
<router-link to="/user/3">User3</router-link>
<div>
<router-view></router-view>
</div>
</p>
</div>
<script type="text/javascript">
const User = {
props:['id','name','age'], //使用props接收路由参数
template:`
<div>
User {{id}} - {{name}} - {{age}}
</div>
`
}
const router = new VueRouter({
routes:[
//如果props是一个对象,它会被按原样设置为组件属性
{path:'/user/:id',component:User,props:{id:'1',name:'snake8859',age:22}}
]
})
const vm = new Vue({
el:"#app",
router
})
</script>
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
# props的值为函数类型
props值为函数,该函数返回的对象中每一组key-value都会通过props传给组件
<div id="app">
<p>
<router-link to="/user/1">User1</router-link>
<router-link to="/user/2">User2</router-link>
<router-link to="/user/3">User3</router-link>
<div>
<router-view></router-view>
</div>
</p>
</div>
<script type="text/javascript">
const User = {
props:['id','name','age'], //使用props接收路由参数
template:`
<div>
User {{id}} - {{name}} - {{age}}
</div>
`
}
const router = new VueRouter({
routes:[
//如果props是一个对象,它会被按原样设置为组件属性
{
path:'/user/:id',
component:User,
//如果props是一个函数,则这个函数接收router对象为自己的形参
props:route=>({id:route.params.id,name:'snake8859',age:22})}
]
})
const vm = new Vue({
el:"#app",
router
})
</script>
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
# 命名路由
为了更加方便的表示路由的路径,可以给路由规则起一个别名,即为“命名路由”。
给路由命名:
{ path:'/demo', component:Demo, children:[ { path:'test', component:Test, children:[ { name:'hello' //给路由命名 path:'welcome', component:Hello, } ] } ] }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17简化跳转:
<!--简化前,需要写完整的路径 --> <router-link to="/demo/test/welcome">跳转</router-link> <!--简化后,直接通过名字跳转 --> <router-link :to="{name:'hello'}">跳转</router-link> <!--简化写法配合传递参数 --> <router-link :to="{ name:'hello', query:{ id:666, title:'你好' } }" >跳转</router-link>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 编程式导航
在页面导航里,有两种方式,声明式导航和编程式导航。
声明式导航
通过点击链接实现导航的方式,例如普通网页中的
<a></a>
链接 或 vue 中的<router-link></router-link>
编程式导航
通过调用JavaScript形式的API实现导航的方式,例如普通网页中的
location.href
或者vue中的this.$router.push('hash地址')
和this.$router.go(n)
# this.$router.push()
用于控制路由的跳转,可传入字符串(路由名称),对象,命名的路由(传递参数)和带查询参数。
<!-- 被 vm 实例所控制的区域 -->
<div id="app">
<router-link to="/user/1">User1</router-link>
<router-link to="/user/2">User2</router-link>
<router-link :to="{ name: 'user', params: {id: 3} }">User3</router-link>
<router-link to="/register">Register</router-link>
<!-- 路由占位符 -->
<router-view></router-view>
</div>
<script type="text/javascript">
const User = {
props:['id','name','age'], //使用props接收路由参数
methods:{
goRegister:function(id){
//字符串
//this.$router.push('/register')
//对象
//this.$router.push({path:'/register'})
//命名路由
//this.$router.push({name:'register',params:{id:id}})
//带参数查询,变成/register?id='1'
//this.$router.push({path:'/register',query:{id:id}})
}
},
template:`
<div>
User {{id}} - {{name}} - {{age}}<br/>
<button @click='goRegister(id)' >跳转注册</button>
</div>
`
}
const Register = {
props:['id'],
template:`
<div>
<h1>User {{id}} 开始注册组件</h1>
</div>
`
}
const router = new VueRouter({
routes:[
{
path:'/user/:id',
//name属性,用于为路由规则起名
name:'user',
component:User,
props:route=>({id:route.params.id,name:'snake8859',age:22})
},
{
path:'/register',
name:'register',
component:Register,
props:true
}
]
})
const vm = new Vue({
el:"#app",
router
})
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
52
53
54
55
56
57
58
59
60
61
62
63
64
65
# this.$router.go(n)
用于路由前进或者后台,参数是一个整数,表示再历史记录中向前或者向后退多少步。
<!-- 被 vm 实例所控制的区域 -->
<div id="app">
<router-link to="/user/1">User1</router-link>
<router-link to="/user/2">User2</router-link>
<router-link :to="{ name: 'user', params: {id: 3} }">User3</router-link>
<router-link to="/register">Register</router-link>
<!-- 路由占位符 -->
<router-view></router-view>
</div>
<script type="text/javascript">
const User = {
props:['id','name','age'], //使用props接收路由参数
methods:{
goRegister:function(id){
//字符串
//this.$router.push('/register')
//对象
//this.$router.push({path:'/register'})
//命名路由
this.$router.push({name:'register',params:{id:id}})
//带参数查询,变成/register?id='1'
//this.$router.push({path:'/register',query:{id:id}})
}
},
template:`
<div>
User {{id}} - {{name}} - {{age}}<br/>
<button @click='goRegister(id)' >跳转注册</button>
</div>
`
}
const Register = {
props:['id'],
methods:{
goBack:function(){
this.$router.go(-1)
}
},
template:`
<div>
<h1>User {{id}} 开始注册组件</h1>
<button @click="goBack">后退</button>
</div>
`
}
const router = new VueRouter({
routes:[
{
path:'/user/:id',
//name属性,用于为路由规则起名
name:'user',
component:User,
props:route=>({id:route.params.id,name:'snake8859',age:22})
},
{
path:'/register',
name:'register',
component:Register,
props:true
}
]
})
const vm = new Vue({
el:"#app",
router
})
</script>
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
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
# 缓存路由
使用内置的<keep-alive>
标签,能够不展示的路由保持挂载,不被销毁。
<!-- 缓存多个路由组件 -->
<keep-alive :include="['News','Message']">
<!-- 缓存一个路由组件 -->
<keep-alive include="News">
<router-view></router-view>
</keep-alive>
2
3
4
5
6
7
# 与路由相关的生命周期函数
路由组件所独有的两个钩子,用于捕获路由组件的激活状态。
# activated
路由组件被激活时触发
activated() {
console.log('News组件被激活了')
}
2
3
# deactivated
路由组件失活时触发
deactivated() {
console.log('News组件失活了')
}
2
3
# 路由守卫
路由守卫的作用就是对路由进行权限控制,按照守卫范围可分为全局守卫、独享守卫和组件内守卫。
# 全局守卫
全局守卫是应用在整个路由内,主要分为:
- 全局前置守卫:初始化执行,每次路由切换前执行
- 全局后置守卫:初始化执行,每次路由切换后执行
//全局前置守卫:初始化时执行、每次路由切换前执行
router.beforeEach((to,from,next)=>{
console.log('beforeEach',to,from)
if(to.meta.isAuth){ //判断当前路由是否需要进行权限控制
if(localStorage.getItem('school') === 'snake8859'){ //权限控制的具体规则
next() //放行
}else{
alert('暂无权限查看')
// next({name:'guanyu'})
}
}else{
next() //放行
}
})
//全局后置守卫:初始化时执行、每次路由切换后执行
router.afterEach((to,from)=>{
console.log('afterEach',to,from)
if(to.meta.title){
document.title = to.meta.title //修改网页的title
}else{
document.title = 'vue_test'
}
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 独享守卫
独享守卫是仅针对某个路由的权限控制,通常写在路由配置文件内,且仅有进入路由时的守卫。
const router = new VueRouter({
routes:[
{
name:'guanyu',
path:'/about',
component:About,
meta:{title:'关于'}
},
{
name:'zhuye',
path:'/home',
component:Home,
meta:{title:'主页'},
children:[
{
name:'xinwen',
path:'news',
component:News,
meta:{isAuth:true,title:'新闻'},
beforeEnter: (to, from, next) => {
console.log('独享路由守卫',to,from)
if(to.meta.isAuth){ //判断是否需要鉴权
if(localStorage.getItem('school')==='snake8859'){
next()
}else{
alert('学校名不对,无权限查看!')
}
}else{
next()
}
}
},
]
}
]
})
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>
<h2>我是About的内容</h2>
</template>
<script>
export default {
name:'About',
mounted() {
// console.log('%%%',this.$route)
},
//通过路由规则,进入该组件时被调用
beforeRouteEnter (to, from, next) {
console.log('About--beforeRouteEnter',to,from)
if(to.meta.isAuth){ //判断是否需要鉴权
if(localStorage.getItem('school')==='snake8859'){
next()
}else{
alert('学校名不对,无权限查看!')
}
}else{
next()
}
},
//通过路由规则,离开该组件时被调用
beforeRouteLeave (to, from, next) {
console.log('About--beforeRouteLeave',to,from)
next()
}
}
</script>
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
# hash模式和history模式
# hash模式
在浏览器经常看到https://ip:port/#/
路径,其中#及其后面的内容就是hash值。
hash值不会包含在 HTTP 请求中,即:hash值不会带给服务器。
优缺点:
- 地址中永远带着#号,不美观 。
- 若以后将地址通过第三方手机app分享,若app校验严格,则地址会被标记为不合法。
- 兼容性较好。
# history模式
正常路径模式https://ip:port/xxx/yyy/zzz
。
history模式虽然地址干净,但是存在一些兼容性和部署上线问题,特别是部署上,当页面刷新时可能会导致404,因为刷新后浏览器会以为发起网络请求,想后端请求数据。通常需要后端人员支持,解决刷新页面服务端404的问题。