Vue全家桶之Vue Router(七)
一 相关理解
1.1 什么是SPA
在讲解Vue Router的概念之前,先了解SPA和路由是什么。以下是SPA的解释:
- 单页
Web
应用(single page web application,SPA
)。 - 整个应用只有一个完整的页面。
- 点击页面中的导航链接不会刷新页面,只会做页面的局部更新。
- 数据需要通过
ajax
请求获取。
1.2 什么是路由
什么是路由?
- 一个路由(
route
)就是一组映射关系(key - value
) key
为路径,value
可能是function
或component
- 多个路由需要路由器(
router
)进行管理
路由分类
① 后端路由:
- 理解:value 是 function, 用于处理客户端提交的请求。
- 工作过程:服务器接收到一个请求时, 根据请求路径找到匹配的函数 来处理请求, 返回响应数据。
② 前端路由:
- 理解:value 是 component,用于展示页面内容。
- 工作过程:当浏览器的路径改变时, 对应的组件就会显示。
1.3 什么是vue-router
vue 的一个插件库,专门用来实现 SPA 应用。
二 基本使用
下面通过一个简单的案例介绍vue-router
的使用。
2.1 效果
左边为导航区,右边为组件内容的显示区。
2.2 实现
安装
vue-router
,命令:npm i vue-router@版本号
- 安装时默认版本为vue-router4,根据需要选择版本
- vue-router4只能在vue3中使用
- vue-router3才可以在vue2中使用
创建
src/router/index.js
,编写router
配置项:
// 该文件专门用于创建整个应用的路由器
//引入vue-router
import VueRouter from 'vue-router'
//引入组件
import About from '../components/About'
import Home from '../components/Home'
//创建并暴露一个路由器
export default new VueRouter({
routes:[
{
path:'/about',
component:About
},
{
path:'/home',
component:Home
}
]
})
在
main.js
引入,并在vue
实例上添加router
配置项
//引入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
})
components
文件夹下两个组件代码
<!-- About.vue -->
<template>
<h2>我是About的内容</h2>
</template>
<script>
export default {
name:'About'
}
</script>
<!-- Home.vue -->
<template>
<h2>我是Home的内容</h2>
</template>
<script>
export default {
name:'Home'
}
</script>
App.vue(所有样式来自Bootstrap,需要提前在
index.html
引入)
<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标签实现路由的切换 -->
<!-- active-class可配置高亮样式 -->
<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 几个注意点
- 路由组件通常存放在
pages
文件夹,一般组件通常存放在components
文件夹。 - 通过切换,“隐藏”了的路由组件,默认是被销毁掉的,需要的时候再去挂载。
- 每个组件都有自己的
$route
属性,里面存储着自己的路由信息。 - 整个应用只有一个
router
,可以通过组件的$router
属性获取到。
三 嵌套路由
-
配置路由规则,使用children配置项:
routes:[ { path:'/about', component:About, }, { path:'/home', component:Home, children:[ //通过children配置子级路由 { path:'news', //此处一定不要写:/news component:News }, { path:'message',//此处一定不要写:/message component:Message } ] } ]
-
跳转(要写完整路径):
<router-link to="/home/news">News</router-link>
四 路由传参
4.1 路由的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>
-
接收参数:
$route.query.id $route.query.title
4.2 命名路由
-
作用:可以简化路由的跳转。
-
如何使用
① 给路由命名:
{ path:'/demo', component:Demo, children:[ { path:'test', component:Test, children:[ { name:'hello' //给路由命名 path:'welcome', component:Hello, } ] } ] }
② 简化跳转:
<!--简化前,需要写完整的路径 --> <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>
4.3 路由的params参数
-
配置路由,声明接收params参数
{ path:'/home', component:Home, children:[ { path:'news', component:News }, { component:Message, children:[ { name:'xiangqing', path:'detail/:id/:title', //使用占位符声明接收params参数 component:Detail } ] } ] }
-
传递参数
<!-- 跳转并携带params参数,to的字符串写法 --> <router-link :to="/home/message/detail/666/你好">跳转</router-link> <!-- 跳转并携带params参数,to的对象写法 --> <router-link :to="{ name:'xiangqing', params:{ id:666, title:'你好' } }" >跳转</router-link>
特别注意:路由携带params参数时,若使用to的对象写法,则不能使用path配置项,必须使用name配置!
-
接收参数:
$route.params.id $route.params.title
4.4 路由的pros配置
作用:让路由组件更方便的收到参数
{
name:'xiangqing',
path:'detail/:id',
component:Detail,
//第一种写法:props值为对象,该对象中所有的key-value的组合最终都会通过props传给Detail组件
// props:{a:900}
//第二种写法:props值为布尔值,布尔值为true,则把路由收到的所有params参数通过props传给Detail组件
// props:true
//第三种写法:props值为函数,该函数返回的对象中每一组key-value都会通过props传给Detail组件
props(route){
return {
id:route.query.id,
title:route.query.title
}
}
}
配置完props
后,在Detail
组件中,不需要去配置计算属性,就能得到简洁的参数。
<template>
<ul>
<li>消息编号:{{id}}</li>
<li>消息标题:{{title}}</li>
</ul>
</template>
<script>
export default {
name:'Detail',
props:['id','title'],
computed: {
// id(){
// return this.$route.query.id
// },
// title(){
// return this.$route.query.title
// },
},
mounted() {
// console.log(this.$route)
},
}
</script>
五 编程式路由导航
5.1 声明式和编程式导航
在学习编程式路由导航之前,先了解下声明式导航和编程式导航的区别。
在浏览器中,点击链接实现导航的方式,叫做声明式导航。例如:
- 普通网页中点击
<a>
链接,vue 项目中点击<router-link>
都属于声明式导航
在浏览器中,调用API方法实现导航的方式,叫做编程式导航。例如:
- 普通网页中调用
location.href
跳转到新页面的方式,属于编程式导航,vue项目中则是借助router
的api实现。
5.2 <router-link>
的replace属性
这里说一下<router-link>
的replace属性,因为等一下的编程式导航中有类似功能的API,所以需要补充说明一下。
- 作用:控制路由跳转时操作浏览器历史记录的模式
- 浏览器的历史记录有两种写入方式:分别为
push
和replace
,存放历史记录的数据结构可以看成是一个栈,push
是向栈中压入新的历史记录,replace
是替换栈顶记录。路由跳转时候默认为push
- 如何开启
replace
模式:<router-link replace .......>News</router-link>
5.3 编程式路由导航API
作用:不借助<router-link>
实现路由跳转,让路由跳转更加灵活
① 两个最常用的API
vue-router 提供了许多编程式导航的API,其中最常用的两个导航API分别是:
this.$router.push('hash地址')
- 跳转到指定 hash 地址,并增加一条历史记录
this.$router.replace('hash地址')
- 跳转到指定的 hash 地址,并替换掉当前的历史记录
<template>
<div>
<h3>Home组件</h3>
<button @click="gotoMessage"> 跳转到Message页面</button>
<button @click="gotoDetail"> 跳转到Detail页面</button>
</div>
</template>
<script>
export default {
name: "Home",
methods: {
//push跳转
gotoMessage() {
this.$router.push('/home/message')
},
//replace跳转
gotoDetail() {
this.$router.replace({
// path:'/home/message/detail'
name:'xiangqing',
query:{
id:m.id,
title:m.title
}
})
}
}
}
</script>
② 三个历史前进、后退的API
this.$router.go(数值n)
- 实现导航历史前进、后退
$router.back()
- 在历史记录中, 后退到上一个页面
$router.forward()
- 在历史记录中, 前进到下一个页面
<template>
<div>
<h3>Home组件</h3>
<button @click="goBack"> 后退 </button>
</div>
</template>
<script>
export default {
name: "Home",
methods: {
goBack(){
// 后退到之前的组件页面
this.$router.go(-1)
}
}
}
</script>
六 缓存路由组件
-
作用:让不展示的路由组件保持挂载,不被销毁。
-
具体编码:
<keep-alive include="News"> <router-view></router-view> </keep-alive>
七 两个生命周期钩子
- 作用:路由组件所独有的两个钩子,用于捕获路由组件的激活状态。
- 具体名字:
activated
路由组件被激活时触发。deactivated
路由组件失活时触发。
接下来,看一个例子,了解缓存路由组件的应用场景和路由组件独有的两个生命周期钩子的使用。
在第二点——基本使用案例的基础上面:
- 令Home组件增加一行透明度变化的文字和几个输入框;
- 要求路由切换时,Home组件不被销毁,输入框已经输入的内容保存;
- 要求路由切换时,控制透明度变化的计时器被清除。
因为要保持Home组件不被销毁,所以App.vue中应该添加keep-alive
标签
<!-- 指定组件的呈现位置 -->
<keep-alive include="Home">
<router-view></router-view>
</keep-alive>
要求组件不被销毁,所以不能使用mounted
钩子去开启定时器,使用beforeDestory
去销毁定时器。
所以Home.vue
组件中应该使用路由中独有的两个生命周期钩子去实现功能。
<template>
<div>
<ul>
<li :style="{opacity}">我是Home的内容</li>
<li>输入框1 <input type="text"></li>
<li>输入框2 <input type="text"></li>
<li>输入框3 <input type="text"></li>
</ul>
</div>
</template>
<script>
export default {
name:'Home',
data() {
return {
opacity: 1
}
},
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)
},
}
</script>
八 路由守卫
-
作用:对路由进行权限控制
-
分类:全局守卫、独享守卫、组件内守卫
-
全局守卫:
//三个参数说明: //to : 即将要进入的目标的路由对象 //from:当前导航即将要离开的路由对象 //next:调用该方法后,才能进入下一个钩子 const router = new VueRouter({ ..... }) //全局前置守卫:初始化时执行、每次路由切换前执行 router.beforeEach((to,from,next)=>{ console.log('beforeEach',to,from) if(to.meta.isAuth){ //判断当前路由是否需要进行权限控制 if(localStorage.getItem('school') === 'atguigu'){ //权限控制的具体规则 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' } })
-
独享守卫:
//在router中的某个route里配置 children:[ { name:'xinwen', path:'news', component:News, meta:{isAuth:true,title:'新闻'}, //只有beforeEnter beforeEnter: (to, from, next) => { console.log('独享路由守卫',to,from) if(to.meta.isAuth){ //判断是否需要鉴权 if(localStorage.getItem('school')==='atguigu'){ next() }else{ alert('学校名不对,无权限查看!') } }else{ next() } } },
-
组件内守卫:
<template> <h2>我是About的内容</h2> </template> <script> export default { name:'About', //通过路由规则,进入该组件时被调用 beforeRouteEnter (to, from, next) { console.log('About--beforeRouteEnter',to,from) if(to.meta.isAuth){ //判断是否需要鉴权 if(localStorage.getItem('school')==='atguigu'){ next() }else{ alert('学校名不对,无权限查看!') } }else{ next() } }, //通过路由规则,离开该组件时被调用 beforeRouteLeave (to, from, next) { console.log('About--beforeRouteLeave',to,from) next() } } </script>
九 路由的两种工作模式
关于hash值
- 对于一个url来说,什么是hash值?—— #及其后面的内容就是hash值。
- hash值不会包含在 HTTP 请求中,即:hash值不会带给服务器。
两种工作模式
使用router的mode配置项可以改变工作模式,默认为hash模式。
- hash模式:
- 地址中永远带着#号,不美观 。
- 若以后将地址通过第三方手机app分享,若app校验严格,则地址会被标记为不合法。
- 兼容性较好。
- history模式:
- 地址干净,美观 。
- 兼容性和hash模式相比略差。
- 应用部署上线时需要后端人员支持,解决刷新页面服务端404的问题。