Vue – vue-router 路由概念与原理
简介
SPA 指的是一个web 网站只有唯一的一个HTML 页面,所有组件的展示与切换都在这唯一的一个页面内完成。 此时,不同组件之间的切换需要通过前端路由来实现。
在 SPA 项目中,不同功能之间的切换,要依赖于前端路由来完成!
前端路由,指的是Hash 地址与组件之间的对应关系!
路由的跳转页面会产生锚点,url 后方会包含 #foo 的锚点,这个 #foo 就是location.hash,所以说前端路由,指的是Hash 地址与组件之间的对应关系!
location.hash = #foo
路由的工作方式
① 用户点击了页面上的路由链接
② 导致了 URL 地址栏中的Hash 值发生了变化
③ 前端路由监听了到Hash 地址的变化
④ 前端路由把当前 Hash 地址对应的组件渲染都浏览器中
简易实现前端路由
本段指简单利用路由方式,但不使用 vue-route 插件的情况下手动实现类路由的功能,真实项目不会使用这种方法。
使用 component 组件
在 template 中建立通用组件元素
<div class="box">
<a href="#/home">主页</a>
<a href="#/left">Left页</a>
<a href="#/right">Right页</a>
</div>
// 使用 component 组件来自由切换组件
<component :is="comName"></component>
导入预设组件
把需要切换的组件都预先导入进来
import Left from "@/components/Left";
import Right from "@/components/Right";
export default {
name: 'App',
data() {
return {
comName: '#'
}
},
components: {
Left,
Right
}
}
在 created 生命周期函数中设定
在页面创建的时候,加入 onhashchange 监听,当 hash 值发生改变的时候,会被执行。
created() {
window.onhashchange = ()=>{
switch (location.hash){
case "#/home":
this.comName = "#"
break;
case "#/left":
this.comName = "Left"
break;
case "#/right":
this.comName = "Right"
break;
}
}
}
这样,当页面点击对应的链接时,就会被监听到 hash 改动,从而改变 comName 的值,从而改变组件的切换。
vue-route 路由安装
vue-router 是 vue.js 官方给出的路由解决方案。它只能结合vue 项目进行使用,能够轻松的管理 SPA 项目 中组件的切换。
步骤
① 安装 vue-router 包
② 创建路由模块
③ 导入并挂载路由模块
④ 声明路由链接和占位符
安装命令
npm i vue-route@3.5.2 -S
创建路由模块
在 src 源代码目录下,新建router/index.js 路由模块,并初始化如下的代码
import Vue from 'vue'
import VueRouter from 'vue-route'
// Vue 安装路由插件
Vue.use(VueRouter)
// 建立一个 VueRouter 路由实例
export default new VueRouter()
在main.js 文件下导入 router 配置文件使其生效
import router from 'router/index'
把注册 router 插件挂载到当前 Vue 实例上
new Vue({
router: router
}).$mount('#app')
vue-router 基本用法
VueRouter 实例中创建路由规则,在 router/index.js 中找到 new VueRouter() 实例对象,VueRouter() 接收一个对象属性,其中路由规则属性名为 routes ,它是一个数组,数组成员为对象,每一个对象代表一条路由规则。
路由规则
路由规则基本写法:
new VueRouter({
routes:[
{ path:'/home', component:Home },
{ path:'/left', component:Left },
{ path:'/right', component:Right },
一个对象表示一条路由规则
]
})
需要用到组件来对应 hash 锚点值,因此要在 router/index.js 中需要引入组件文件。
import Home from "@/components/Home";
import Left from "@/components/Left";
import Right from "@/components/Right";
template 占位组件
路由提供一个用于占位的组件
<router-view></router-view>
放到需要切换组件的 template 位置中即可。
使用 router-link 代替 a 标签
vue-router 提供了用于切换锚点的标签 router-link,代替 a 标签,官方推荐使用 router-link
<a href="#/home">首页</a>
router-link 实际上是经过包装的 a 标签
<router-link to="/home">首页</router-link>
router-link 的 to 属性,和 a 标签的 href 是一样的
路由重定向
用户在访问地址 A 的时候,强制用户跳转到地址C ,从而展示特定的组件页面。 通过路由规则的redirect 属性,指定一个新的路由地址,可以很方便地设置路由的重定向。
使用 redirect
来实现路由重定向
routes:[
// 路由重定向
{ path:'/', redirect:'/home' },
// 路由规则
{ path:'/home', component:Home },
{ path:'/left', component:Left },
{ path:'/right', component:Right },
]
嵌套子级路由
路由可以让父组件方便地切换不同的子组件,但是子组件也有需要自由切换孙组件,这时的路由需要实现嵌套路由。
通过 children 属性声明子路由规则
{
path:'/left', component:Left,
// Left 组件下的子路由规则
children:[
{ path:'tab1', component:Tab1 },
{ path:'tab2', component:Tab2 }
]
},
注意:父组件的 path 可以增加 '/' ,但是 children 子组件中的路由中的 path 不可增加 '/'.
默认孙组件重定向
当父组件点击子组件路由时,第一次切换时没有默认孙组件路由,因此我们要在子组件中设置默认的子路由。在子组件路由规则中加入重定向属性即可。
{
path:'/left', component:Left,
// 设定子路由载入后默认显示 tab1 孙组件
redirect: '/left/tab1',
// Left 组件下的子路由规则
children:[
{ path:'tab1', component:Tab1 },
{ path:'tab2', component:Tab2 }
]
},
默认子路由
除了上面使用 redirect
属性设定重定向孙组件的方法外,VueRouter 还规定了,如果子路由中某一条规则 path 为空时,则认为它就是默认子路由组件。
{
path:'/left', component:Left,
// Left 组件打开后,Tab1 为默认显示组件
children:[
{ path:'', component:Tab1 },
{ path:'tab2', component:Tab2 }
]
},
router-link 中的 to 属性可以不写子组件的位置
<router-link to="/left"></router-link>
设置路由高亮[3.0]
在路由链接点击时,Vue-router 提供让由激活的路由链接设置高亮样式,也支持自定义样式名。
使用默认的高亮 class 类
被激活的路由链接,默认会应用一个叫做 router-link-active 的类名。开发者可以使用此类名选择器,为激活的路由链接设置高亮的样式
.router-link-active {
background-color: red;
color: white;
font-weight: bold;
}
自定义路由高亮的 class 类
在创建路由实例对象时,开发者可以基于 linkActiveClass 属性,自定义路由链接被激活时用应用的类名。
const router = createRouter({
history: createWebHashHistory(),
// 指定被激活的路由链接,会应用 router-active 这个类名
// 默认的 router-link-active 类名会被覆盖掉
linkActiveClass: 'router-active',
})
css 样式
.router-active {
background-color: red;
color: white;
font-weight: bold;
}
动态路由
动态路由指的是:把Hash 地址中可变的部分定义为参数项,从而提高路由规则的复用性。 在 vue-router 中使用英文的冒号(:)来定义路由的参数项。
通过 动态参数 来代替多个变化的路由,使用 : 号加代替传过来的参数,如 :id。
该 :id 的值将保存在 this.$route.param
对象中,在实例中可以通过this.$route.param.id
获得该id值。
在 template 中,默认帮你自动填充 this 对象,可以直接$route.param.id
获得值。
使用 props 接收动态参数值
在 template 中使用 $route.param.id
来获得值非常麻烦,但可以通过组件接收 props 来实现直接接收参数。
只需在路由规则中加入属性 { props: true }
则可在相关组件中的 props 中接收到该占位符的数值。
{ path: 'movie/:foo', component: Movie, props: true }
props:[ 'foo' ]
foo 将接收 :foo 的值
使用对象型,定义传参,可在路由页面中使用props接收
props: {
keyword: this.$route.params.keywork,
queryKey: this.$route.query.k
}
使用函数方法,函数中返回一个对象,可在路由页面中使用props接收
props: ($route) => {
return {
keyword: $route.params.keyword,
}
}
动态路由中的可选传参
当动态路由中的参数有可能不被传递时,会产生路由警告,可在动态参数后增加【?】定义参数可选传递。
{ path: 'movie/:foo?', component: Movie, props: true }
注意:可选传参中的【?】只支持当【:foo】为 undefined 时才能生效,但一般如果【:foo】参数是由 v-model 双向绑定时,当【:foo】不传值时,【:foo】为 "" 而不是 undefined .这使得【?】可选传参功能失效。
因此,在传参时,为了避免传到空值,应当先判断【:foo】是否为 "" ,如果为 "" ,应当设置为 undefined
this.$router.push({
name: "search",
params: {
keyword: this.keyword || undefined
},
});
查询参数
传入参数值有两种类型,一种是 /foo 的参数值,如上一节所写的,使用 this.$route.param
来获得。
还有另一种传参数值的类型,则是平常我们常见的 /foo?name=zx&age=18 这样的参数值,其中的 name=zx&age=18 则是查询参数值,如何获得这样的参数值呢?
我们可以通过使用 this.$route.query
来获取,query 是一个对象,其包含所有查询参数值
this.$route.query ==>
{ name:'zx', age: '18' }
声明式导航 与 编程式导航
声明式导航
在浏览器中,点击链接实现导航的方式,叫做声明式导航。例如:
普通网页中点击<a> 链接、vue 项目中点击 <router-link> 都属于声明式导航。
编程式导航
在浏览器中,调用 API 方法实现导航的方式,叫做编程式导航。例如:
普通网页中调用 location.href 跳转到新页面的方式,属于编程式导航。
常见编程式导航 API
this.$router.push('hash 地址')
跳转到指定hash 地址,并增加一条历史记录
methods:{
push(){
this.$router.push('foo/1')
}
}
this.$router.replace('hash 地址')
跳转到指定的hash 地址,并替换掉当前的历史记录
methods:{
replace(){
this.$router.replace('foo/1')
}
}
注意:replace前进后,会清空或覆盖后面的历史浏览记录。
this.$router.go(数值 n)
实现导航历史前进、后退
methods:{
go(){
this.$router.go(-1)
}
}
注意:go(-1) 代表后退一个页面,go(1) 代表前进一个页面,go(-2) 代表后退两个页面,如此类推。
go() 的简写方法
通常我们比较少使用 go( -2 ) 这样跳过两层页面的操作,更多的是后退一页或前进一页,因此 vue-router 提供了两个直接的方法来实现后退一页或前进一页的操作。
this.$router.back()
可以实现后退一页,用在 @click 中不可以加入 this,在方法中需要加入 this
<button @click="$router.back()"></button>
this.$router.forward()
可以实现前进一页,用在 @click 中不可以加入 this,在方法中需要加入 this
<button @click="$router.forward()"></button>
导航守卫
导航守卫可以控制路由的访问权限。
全局前置守卫
每次发生路由的导航跳转时,都会触发全局前置守卫。因此,在全局前置守卫中,程序员可以对每个路由进行 访问权限的控制。
// 创建路由实例对象
const router = new VueRouter({ ... })
// 为 router 实例对象,声明全局前置导航守卫
// 只要发生了路由的跳转,必然会触发 beforeEach
router.beforeEach( function( to, from, next){} )
假如页面A要跳到页面C,则
to: 指的是 页面C.就是将要访问的页面。to 为一个 $route 对象
from: 指的是 页面A.就是当前页面。from 为一个 $route 对象
next: 它是 vue-router 提供的一个函数,当调用 next() 时,则表示允许跳转到 to 所在的页面。在这里不调用 next() 所有路由跳转将失效。
next() 三种调用方式
当前用户拥有后台主页的访问权限,直接放行:next()
当前用户没有后台主页的访问权限,强制其跳转到登录页面:next('/login')
当前用户没有后台主页的访问权限,不允许跳转到后台主页:next(false)
控制访问权限
全局解析守卫
全局解析守卫是Vue-Router 2.5 之后推出的功能,功能类似于【全局前置守卫】。
声明全局解析守卫如下:
/**
* 路由全局解析守卫
*/
router.beforeResolve((to, from, next) => {
next();
});
全局解析守卫将在全局前置守卫执行之后被调用。
全局后置钩子
全局后置钩子,指的是当全局前置守卫被放行之后被调用,页面已经实行跳转操作,此时的页面已经被允许跳转,所以全局后置钩子没有 next() 放行方法,作用是在跳转之后,对页面路由作最后的加工处理。
let router = new
/**
* 全局后置钩子
*/
router.afterEach((to, from) => {
})
路由独享的守卫
路由独享的守卫是配置在单独的页面路由上的,这样的守子只会在访问到该路由页面时,才会被触发路由守卫。定义如下
export default {
path: "/login",
name: "login",
component: Login,
meta: {
headerShow: true,
footerShow: true
},
// 路由独享守卫,定义在页面路由信息上,只会在访问这个路由时,才会被触发。
// 本例中的 login 路由,定义了路由独享守卫,则在访问 /login 时才会触发守卫判断
beforeEnter(to, from, next) {
// 判断是否已登陆,如果登陆了,就跳到Home首页
}
}
组件内的守卫
组件内的守卫 不定义在 路由规则上,而是作为一个【VueComponent 生命周期】的形式进行触发调用,方法定义在组件中:
<script>
export default {
name: 'Login',
data() {
return {
username: "",
password: ""
}
},
/**
* beforeRouteEnter 准备跳转到这个路由页面时调
* 因为 beforeRouteEnter 调用时,该页面还没有被确认并创建完成,因此无法获取 "this"
* @param to
* @param from
* @param next
*/
beforeRouteEnter(to,from,next){},
/**
* beforeRouteLeave 当路由页面即将被跳转出去之前时调用
* beforeRouteLeave 通常用于判断用户是否在未保存数据时关闭页面,作一个拦截操作
* 可以获取 "this"
* @param to
* @param from
* @param next
*/
beforeRouteLeave(to,from,next){},
/**
* beforeRouteUpdate 路由页面参数被改变时,页面重新刷新时被调用
* 如:/foo/:id 的路由,当 /foo/1 => /foo/2 等参数发生改变,路由被跳转但只路转给本路时调用。
* 可以获取 "this"
* @param to
* @param from
* @param next
*/
beforeRouteUpdate(to,from,next){},
}
</script>
关于 beforeRouteEnter 阶段获取 this 的问题
我们知道 beforeRouteEnter 阶段是在路由准备跳转时的阶段,这时vc对象还没被创建,无法获取 this 对象。
但如果我们需要在 beforeRouteEnter 阶段获取 this 该怎么做?
VueRouter 在 beforeRouteEnter 中的 next() 提供了获取 vm 的方法。
beforeRouteEnter (to, from, next) {
next(vm => {
// 通过 `vm` 访问组件实例
})
}
注意:只有 beforeRouteEnter 的 next() 才有 vm 回调函数,其它钩子方法是没有的。
完整流程
- 导航被触发。
- 在失活的组件里调用
beforeRouteLeave
守卫。 - 调用全局的
beforeEach
守卫。 - 在重用的组件里调用
beforeRouteUpdate
守卫 (2.2+)。 - 在路由配置里调用
beforeEnter
。 - 解析异步路由组件。
- 在被激活的组件里调用
beforeRouteEnter
。 - 调用全局的
beforeResolve
守卫 (2.5+)。 - 导航被确认。
- 调用全局的
afterEach
钩子。 - 触发 DOM 更新。
- 调用
beforeRouteEnter
守卫中传给next
的回调函数,创建好的组件实例会作为回调函数的参数传入。
共有 0 条评论