vue-route
web应用的页面
传统 web 应用:又叫做多页面 web 应用。核心是一个 web 站点由多个 HTML 页面组成,点击时完成页面的切换, 因为是切换到新的 HTML 页面上,所以当前页面会全部刷新。
单页面 web 应用:(SPA:Single Page web Application)。加载单个 HTML 页面并在用户与应用程序交互时动态更新该页面的 Web 应用程序。
浏 览器一开始会加载必需的 HTML、CSS 和 JavaScript,所有的操作都在这张页面上完成,都由 JavaScript 来控制。 单页面的跳转仅刷新局部资源。因此,对单页应用来说模块化的开发和设计显得相当重要。
SPA的优点:
- 提供了更加吸引人的用户体验:具有桌面应用的即时性、网站的可移植性和可访问性。
- 单页应用的内容的改变不需要重新加载整个页面,web 应用更具响应性和更令人着迷。
- 单页应用没有页面之间的切换。不会出现“白屏现象”,也不会出现假死并有“闪烁”现象
- 单页应用相对服务器压力小,服务器只用出数据就可以,不用管展示逻辑和页面合成,吞吐能力会提高几倍。
- 良好的前后端分离。后端不再负责模板渲染、输出页面工作,后端 API 通用化,即同一套后端程序代码,不用修改就可以用于 Web 界面、手机、平板等多种客户端
SPA的缺点:
- 首次加载耗时比较多。
- SEO 问题:不利于百度,360 等搜索引擎收录。
- 容易造成 CSS 命名冲突。
- 前进、后退、地址栏、书签等,都需要程序进行管理,页面的复杂度很高,需要一定的技能水平和开发成本 高。
传统web应用与SPA的对比:

- SPA是目前较为流行的。
- 使用vue来开发SPA,需要借助vue的路由机制(vue-route插件)。
路由机制概述
vue支持组件化开发,故路由机制确实就是根据地址(uri)动态渲染特定的组件(路由组件)。
路由:route。
一个路由对应着一个 地址 ==> 路由组件 的映射。即 path ==> component 的映射。
路由器:router。
一个SPA只需要一个router,用来管理全局的路由route。

路由组件
路由组件由 vue-route 进行管理;普通组件由父组件进行管理。
路由组件通常存放在
pages/下;普通组件通常存放在components/下。默认情况下,当路由组件在进行切换的时候,切掉的组件实例会被销毁。
路由组件实例和普通组件实例都会多两个属性:
$route和$router:this.$route // 属于自己的路由对象:封装了路由到这个组件的相关信息 this.$router // 路由器对象,负责管理全局的路由。即 new VueRouter(...)
路由的使用
安装vue-route
# vue2 npm i vue-router@3 # vue3 npm i vue-router@4编写 router/index.js 文件,创建并暴露路由器对象 router
// 导入vue-router插件 import VueRouter from "vue-router" // 导入路由组件 import HeBei from '../pages/HeBei.vue' import HeNan from '../pages/HeNan.vue' // 创建路由器对象(在路由器对象中配置路由。) const router = new VueRouter({ // 在这里配置所有的路由规则。 routes : [ // 这就是一个路由(一级路由) { // 只要路径监测到的是 /hebei,就切换到HeBei组件. path : '/hebei', // 一级路由的路径要以 / 开始 component : HeBei }, // 这是另一个路由 { path : '/henan', component : HeNan } ] }) // 暴露路由器对象(导出路由器对象) export default router便于表述,将 index.js 中的路由配置称为路由配置对象,而路由组件实例的
$route则称为路由对象。$route是由对应的 路由配置对象 扩充而来,保留有路由配置对象的大部分属性。main.js 中引入并使用 vue-route
// 导入vue-router插件 import VueRouter from 'vue-router' // 导入路由器对象 //import router from './router/index.js' import router from './router' // 简写形式:省略 /index.js // 使用vue-router插件 Vue.use(VueRouter) // 使用后,vm配置项会多出一个 router 配置项,用来指定路由器对象 new Vue({ el : '#app', router : router, render : h => h(App) })在组件中使用路由机制
<div> <div> <h1>省份</h1> <ul> <!-- 如果使用的是路由方式,就不能使用超链接a标签了,需要使用vue-router插件提供的一个标签 --> <!-- router-link 将来会被自动编译为a标签。 --> <li><router-link to="/hebei" active-class="selected">河北省</router-link></li> <li><router-link to="/henan" active-class="selected">河南省</router-link></li> <!-- active-class用来指定激活时的类,激活:即点击了 --> </ul> </div> <!-- 路由视图,其实就是起到一个占位的作用。 --> <router-view></router-view> </div>- 当点击超链接时(编译后会生成),路由器检测到路径变为 /hebei,查找路由匹配路径path后,路由到 HeBei 这个组件,并将这个组件渲染至路由视图
<router-view>上。
- 当点击超链接时(编译后会生成),路由器检测到路径变为 /hebei,查找路由匹配路径path后,路由到 HeBei 这个组件,并将这个组件渲染至路由视图
多级路由
除了一级路由外,vue-route 还支持配置多级路由,在路由配置中使用 children 配置项,即可实现。

// 一级路由们
routes : [
// 一级路由,path以 / 开头
{
path : '/hebei',
component : HeBei,
// 子路由们
children : [
// 这是其中的一个子路由
{
// 注意:对于子路由来说,path不要以 / 开始,这个 / 系统会自动添加。
path : 'shijiazhuang',
component : ShiJiaZhuang
// children : [ ... ]
},
{
path : 'handan',
component : HanDan
// children : [ ... ]
}
]
},
{
path : '/henan',
component : HeNan
}
]<router-link to="/hebei/shijiazhuang" active-class="selected">河北省石家庄市</router-link>当路由器监测到路径为 /hebei/shijiazhuang 时,首先会根据 /hebei 匹配到一级路由,然后逐级向下匹配子路由,最终路由至 ShiJiaZhuang 这个组件,创建路由组件实例,并渲染至路由视图 <router-view>
路由的命名
一个路由是 path ==> component 的映射,多级路由下,path会很长,为了便于表示路由,可以在路由配置对象中给路由起名(name),在需要的地方通过这个name既可以确定路由的路径。即 name ==> path ==> component。
routes : [
{
path : '/hebei',
component : HeBei,
children : [
{
// 给路由命名
name : 'shi',
path : 'sjz',
component : City
}
]
}
]使用name,即可简化路由的使用:
<router-link active-class="selected" :to="{
// 注意:如果使用name的话,to赋值必须使用对象形式。
name : 'shi',
// 自动根据名字确定路由路径
// path : '/hebei/sjz
}">
石家庄
</router-link>路由配置中的 meta
路由配置对象可以接收一个 meta 配置项,meta 配置项会作为一个对象挂载至路由对象(当路由到此配置时):
// 路由配置对象
{
name : 'shi',
path : 'sjz/:a1/:a2/:a3',
component : City,
props : true,
meta : {
// 路由鉴权标识
isAuth : true,
title : '石家庄'
}
}当路由到此配置时,路由对象(如$route )会保留 meta 对象作为属性;
this.$route.meta.isAuth // true
this.$route.meta.title // '石家庄'路由组件的参数传递
query传参
字符串形式(给to赋值字符串)
通过在url后追加
?name=value,...实现,vue-route会自动解析这些数据,封装在路由组件实例的this.$route.query对象中。<router-link to="/hebei/city?a1=长安区&a2=裕华区&a3=新华区">石家庄</router-link> <!-- ES6字符串模板语法 + v-bind指令 --> <router-link :to="`/hebei/city?a1=${sjz[0]}&a2=${sjz[1]}&a3=${sjz[2]}`">石家庄</router-link>对象形式(给to赋值对象)
通过给 to 传递一个对象(使用v-bind)实现。
<router-link active-class="selected" :to="{ path : '/hebei/city', // 单独定义 query 对象,直接传赋值给 $route.query query : { a1 : '长安区', a2 : '裕华区', a3 : '新华区', } }">石家庄</router-link>
无论使用哪种方式进行query传参,最终都会回显至地址栏:/example?param1=value1¶m2=value2。
路由组件通过实例 $route.query 对象获取这些参数:
this.$route.query.a1 // "长安区"
this.$route.query.a2 // "裕华区"
this.$route.query.a3 // "新华区"query传参不需要修改路由配置(即 index.js)。只需要修改 to 的赋值即可。
params传参
与 query 方式参数名、参数个数不确定(可以单独发起get请求,携带自定义参数,此时参数不可控)不同,params方式允许指定参数名和参数个数。
字符串形式(给to赋值字符串)
需要先在路由配置对象中指定参数名和个数:
routes : [ { path : '/hebei', component : HeBei, children : [ { name : 'shi', // 在 path 中指定路由接受的参数和个数 // :参数名/:参数名/... path : 'sjz/:a1/:a2/:a3', // 便于表述,记赋值了的 /:a1/:a2/:a3 为参数路径 component : City } ] } ]具体的参数值由请求路径决定。当匹配到路由时,剩余的 uri 路径会自动作为参数值。
如:
/hebei/sjz/长安区/裕华区/新华区匹配到 City组件后,剩余的/长安区/裕华区/新华区会分别赋值给 a1、a2、a3。- 如果没有在路由配置中指定参数名和个数,则参数路径会继续查找路由,导致数据接收失败、路由期望不一致。
- 如果路径中的参数个数与路由配置中的不一致(缺少或更多),则也会继续查找路由。
配置带参数值的路径即可:
to="/hebei/sjz/长安区/裕华区/新华区" // /长安区/裕华区/新华区 即为参数路径 // 使用模板字符串 :to="`/hebei/sjz/${sjz[0]}/${sjz[1]}/${sjz[2]}`"params + str 会在地址栏回显数据。
对象形式(给to赋值对象)
to="{ // 强调:如果使用的是params方式传参,这里只能使用name,不能使用path // !!!path : '/hebei/sjz', name : 'shi', params : { a1 : sjz[0], a2 : sjz[1], a3 : sjz[2], } }"params + obj 中,配置参数路径不是必需的。
配置了路径参数:
- 数据不少于占位符时,数据会回显地址栏,且回显个数为路径参数占位符的个数,所有数据正常接收(?额外数据的参数名不定)。
- 数据少于占位符时,地址栏地址会重置,但数据能接收,且页面功能不影响。
- 如果使用的是path而不是name,接收不到参数。
没有配置路径参数:
- 不论数据多少,都能正常接收,都不会回显地址栏。
- 如果使用的是path而不是name,接收不到参数。
路由组件通过实例 $route.params 对象获取这些参数:
this.$route.params.a1 // "长安区"
this.$route.params.a2 // "裕华区"
this.$route.params.a3 // "新华区"路由的 props
vue-route 管理路由组件时,底层实例化路由组件时,可以通过组件间的props方式进行数据传递。
// A组件
propos : ['a1']
// B组件在模板中使用A组件时,可以给props传值
<A a1="1"></A>路由环境下,路由组件的实例化交由vue-route负责,故prop的赋值就需要在路由配置对象中完成。
// 路由组件中,需要提供 props 配置项来完成数据的接收
props : ['x','y']静态prpos赋值
{ name : 'shi', path : 'sjz/:a1/:a2/:a3', component : City, // 在路由配置中使用 props配置项完成 // 静态方式,将组件实例的 props 赋值写死 props : { x : '张三', y : '李四' } },函数式props赋值
// props函数式写法会自动接收路由组件实例的 $route 对象作为参数 // vc.$route 将来会被自动传过来,它代表了当前的路由对象。形参名随意。 props($route){ // 返回值作为组件实例 props 赋值的依据 return { x : $route.params.a1, y : $route.params.a2 } }自动化props赋值
// 自动化 props 赋值,会将 vc.$route.params 对象作为组件实例props赋值的依据 // 仅支持 params 方式,底层自动转换,需要的话直接开启即可 props : true
路由的模式
浏览器中,浏览的历史记录(完整的url地址)是以栈数据结构进行保存的。这个栈对待新增的历史记录有两种处理方式:
- push 模式:即将新的记录 push 至栈顶,同时移动当前url的指针至新记录
- replace 模式:将新的记录替换栈顶元素,当前url指针不动,但指向的是新栈顶。

在路由过程中,使用 to 的声明式路由默认是 push 模式,可以添加 replace 属性表示这个路由是 replace 模式。即替换栈顶元素:
<!-- route-link 启用 replace 模式 -->
<!--使用v-bind-->
<router-link to="..." :replace="true" ></router-link>
<!--简写-->
<router-link to="..." replace ></router-link>使用 api 操作进行当前记录实现前进或后退:
前进/后退:

this.$router.forward() // 前进1步
this.$router.back() // 后退1步
this.$router.go(2) // 前进2步
this.$router.go(-2) // 后退2步编程式路由
通过<router-link>的 to 属性发起的路由,称为声明式路由导航。
在组件中,也可以通过 $router 路由器发起路由,称为编程式路由导航。
// 在一个组件中,执行一段特定的代码实现编程式路由(如在事件的handler中执行)
// 注意:在使用编程式的路由导航时,push以及replace方法会返回一个Promise对象
// Promise对象期望你能通过参数的方式给它两个回调函数,一个是成功的回调,一个是失败的回调。
// 如果你没有给这两个回调函数,则会出现错误。
// $router.push|replace(toObj, successCallback, errorCallback)
// toObj 即声明式路由中,给 to 属性赋值的对象
this.$router.push({
name : 'shi',
params : {
a1 : this.sjz[0],
a2 : this.sjz[1],
a3 : this.sjz[2],
}
}, ()=>{}, ()=>{})路由组件的缓存
默认情况下,当路由视图 <router-view> 刷新时,原先路由视图渲染的路由组件会直接销毁 destory。有时,需要在路由视图切换时,保留住原先的路由组件实例,此时就需要使用路由组件的缓存(keep-alive 保活)。
<!-- 路由到任一路由组件都缓存 -->
<keep-alive>
<router-view></router-view>
</keep-alive>
<!-- 只路由到特定路由组件时缓存 -->
<!-- 注意:include="HeBei"表示在进行路由组件切换的时候,HeBei组件不被销毁。保留状态。 -->
<keep-alive include="HeBei">
<router-view></router-view>
</keep-alive>
<!-- 只路由到特定路由组件时缓存(数组形式,配合v-bind) -->
<keep-alive :include="['HeBei', 'HeNan']">
<router-view></router-view>
</keep-alive>组件的名称:'HeBei', 'HeNan',指组件 .vue 文件中配置的 name 属性值。
export default {
name :'HeBei'
...
}路由组件的额外生命周期
对于路由组件来说,处理普通组件的生命周期钩子外,还有两个生命周期钩子:activated、deactivated。分别在路由组件实例渲染后和取消渲染后执行。
当开启路由组件实例保活时,组件的定时器等仅需要在组件渲染时启用,此时就可以在 activated 钩子中开启,在 deactivated 中结束。
这样即实现了功能的需要,又能保活提高用户体验。
activated(){
// 开启定时器
this.timer = setInterval(() => {
console.log('我是一个定时器!')
}, 1000)
console.log('激活了')
},
deactivated(){
// 删除定时器
clearInterval(this.timer)
console.log('失效了')
}与 destoryed 钩子不同,deactivated 是路由视图切换时(即当前组件渲染结束时)调用(暂时不使用),而 destoryed 在组件实例销毁时被调用(一直都不再使用)。面向的使用场景不一样。
- 通常activated、deactivated钩子配合
<keep-alive>使用。
路由守卫
和过滤器相似,路由守卫是负责在路由前和后执行特定代码的一段程序,通常用来用户鉴权、对路由进行增强(类AOP)等。
路由守卫右全局、局部之分:
- 全局路由守卫
- 全局前置守卫
- 全局后置守卫
- 局部路由守卫
- path守卫
- component守卫
不同的路由守卫的关注点不一样,代码的执行时机也不一样,选择合适的路由守卫可以便于程序功能的实现。
全局前置守卫
全局前置路由守卫至多1个,每当进行路由时,都会经过全局前置守卫,并由全局前置守卫决定是否继续路由。
- 初始化(初次访问)时也会经过全局前置守卫。
配置全局前置守卫,需要通过 router 路由器对象:
// index.js
const router = new VueRouter({
// ...
})
// 一般在创建router对象后,导出router对象前配置全局守卫(前置或后置)
// 配置全局前置守卫,传入一个callback,接收三个参数
// to、from:表示起点、目标的 路由对象($route)
// next:执行 next() 表示放行,继续路由。不执行next函数,则路由终止
router.beforeEach((to, from, next) => {
// 以鉴权为例
// 假设当前登录的用户是:
let loginName = 'admin'
// 通过在路由配置对象中配置 meta : { isAuth :true },表示这个路由需要鉴权,不提供或为false则表示不需要鉴权
if(to.meta.isAuth){
// 鉴权的逻辑
if(loginName === 'admin'){
// 鉴权通过,放行
next()
}else{
alert('对不起,您没有权限!')
}
}else{
// 路由目标无需鉴权,放行
next()
}
})
export default router全局后置守卫
全局前置路由守卫也至多1个,每当路由结束时,都会经过全局前置守卫,执行全局后置守卫中编写的代码,完成路由结束的一致的收尾工作。
- 初始化结束(初次访问页面渲染结束)时也会经过全局后置守卫。
全局后置守卫和全局前置守卫类似,也需要通过router对象,在router导出前实现:
// index.js
// afterEach(callback)
// callback 可以接收两个参数:to, from。是目标、起点的route对象
// 因为不负责放行,故没有next参数。
router.afterEach((to, from) => {
// || 运算符,当左真值为false时,取右值(不转真值)
document.title = to.meta.title || '欢迎使用本系统'
})
export default routerpath守卫
一个路由对应一个路径path,是局部的,即path守卫。
一个path守卫是守卫一个path的,只有路由到这个path时,才会经过这个path守卫,并由这个守卫决定是否放行(通常是最后的放行,即允许访问)。
path守卫在路由配置对象中指定,以函数属性的形式存在。
{
name : 'shi',
path : '/hebei/sjz',
component : City,
meta : {
isAuth : true,
title : '石家庄'
},
// 参数:to from next,同全局前置守卫中的参数
beforeEnter(to, from, next){
let loginName = 'admin'
// 需要时直接鉴权即可,无需判断是否有鉴权标识
if(loginName === 'admin'){
// 鉴权通过,放行
next()
}else{
alert('对不起,您没有权限!')
}
},
// 没有afterEnter这一说。path守卫只有 beforeEnter
// afterEnter(){}
}component守卫
和path守卫类似,component守卫也是局部守卫。但component守卫是专门守卫特定的路由组件,component守卫有两个配置,当路由到这个路由组件时(组件实例化前),会先经过其中一个配置,决定是否放行(渲染组件);路由结束后(渲染结束后)会经过另一个配置,决定是否放行(允许进行下一次路由)。
在组件的配置对象中进行component守卫的配置:
// .vue
export default {
// beforeRouteEnter执行时机:进入路由组件之前执行。
// 普通组件不会触发,必须是路由组件才会触发。
beforeRouteEnter(to, from, next){
console.log(`进入路由组件之前:${to.meta.title}`)
// this 是 undefined。表名vc还未实例化
next() // 渲染组件
},
// beforeRouteLeave执行时机:离开路由组件之前执行。
// 普通组件不会触发,必须是路由组件才会触发。
beforeRouteLeave(to, from, next){
console.log(`离开路由组件之前:${from.meta.title}`)
next() // 进行下一次路由
}
}