1.前言
上篇文章说了vue-router的路由命名视图,动态路由匹配,路由滚动行为,接下来再看看其他的用法
2.路由过渡效果
过渡效果类似css中的过滤,我们用的是transititon的封装组件来为路由添加过渡动画的,首先来看个图了解下有哪些过渡的css类名,Enter就是路由的进入,是从Opacity 0到1(淡入),Leave就是路由离开是从Opacity 1到0(淡出),v-enter-active,v-leave-active是活动的状态,可以被用来定义过渡的时间,延迟和曲线函数等
v-enter: 定义进入过渡的开始状态
v-enter-active: 定义进入活动状态
v-enter-to: 定义进入的结束状态
v-leave: 定义离开过渡的开始状态
v-leave-active: 定义离开活动状态
v-leave-to: 定义离开的结束状态
下面我们来简单的模拟一个淡入淡出,过渡为1秒的效果,看下面代码
<template>
<div class="router-class">
<div>
<router-link to="/" exact>home页面</router-link>
<router-link to="/about" active-class="about-router"
>about页面</router-link
>
<router-link to="/help" active-class="help-router"
>help页面</router-link
>
<router-link to="/user" active-class="help-router">user页面</router-link>
</div>
<transition mode="out-in">
<router-view class="default"></router-view>
</transition>
</div>
</template>
<style>
.v-enter {
opacity: 0;
}
.v-enter-to {
opacity: 1;
}
.v-enter-active {
transition: 1s;
}
.v-leave {
opacity: 1;
}
.v-leave-to {
opacity: 0;
}
.v-leave-active {
transition: 1s;
}
</style>
上面用了mode为out-in的过渡模式!
out-in: 当前元素先进行过渡,完成之后新元素过渡进入
in-out: 新元素先进行过渡,完成之后当前元素过渡离开
如果不想用v开头的class,我们想自定义样式就需要用到name这个属性,我们可以定义left,right,然后动态的设置name属性,我们可以设置路由的元信息 (index),可以根据目标路由和正要离开的路由的index比较来判断到底用的是left还是right过滤效果,我们先来为路由设置元信息
export default new Router({
linkActiveClass: "active",
mode: 'history',
routes: [{
path: '/',
component: Home,
meta:{
index:0
},
},
{
path: '/home',
name: 'home',
components: {
default: Home,
sidebar: Sidebar
}
},
{
path: '/about',
component: About,
children: [{
path: '',
name: 'work',
component: Work,
meta:{
index:1
},
},
{
path: 'company',
name: 'company',
component: Company,
},
{
path: 'contactUs',
name: 'contactUs',
component: ContactUs,
}
]
},
{
path: '/help',
name: 'help',
alias: "/123",
component: Help,
meta:{
index:2
},
},
{
path: '/user/:id?',
name: 'user',
component: User,
meta:{
index:3
},
}
]
})
然后设置transition的name属性,注意这里是动态设置,所以需要加上冒号
<transition name="className">
<router-view class="default"></router-view>
</transition>
最后监听$route
export default {
name: "App",
data() {
return {
className: "",
};
},
watch: {
$route(to, from) {
console.log(to.meta.index);
console.log(from.meta.index);
//目标的路由index大于离开路由的index就用left效果,否则用right效果
this.className = to.meta.index > from.meta.index ? "left" : "right";
},
},
};
看下整个代码
<template>
<div class="router-class">
<div>
<router-link to="/" exact>home页面</router-link>
<router-link to="/about" active-class="about-router"
>about页面</router-link
>
<router-link to="/help" active-class="help-router"
>help页面</router-link
>
<router-link to="/user" active-class="help-router">user页面</router-link>
</div>
<transition :name="className">
<router-view class="default"></router-view>
</transition>
</div>
</template>
<script>
export default {
name: "App",
data() {
return {
className: "",
};
},
watch: {
$route(to, from) {
console.log(to.meta.index);
console.log(from.meta.index);
this.className = to.meta.index > from.meta.index ? "left" : "right";
},
},
};
</script>
<style>
.active {
background-color: yellow;
}
.about-router {
background-color: pink;
}
.help-router {
background-color: skyblue;
}
.sidebar {
width: 250px;
height: 1000px;
float: left;
border: 1px solid #aaa;
}
.default {
height: 2000px;
border: 1px solid #aaa;
}
.left-enter {
transform: translateX(-100%);
}
.left-enter-to {
transform: translateX(0);
}
.left-enter-active {
transition: 1s;
}
.left-leave {
transform: translateX(0);
}
.left-leave-to {
transform: translateX(100%);
}
.left-leave-active {
transition: 1s;
}
.right-enter {
transform: translateX(100%);
}
.right-enter-to {
transform: translateX(0);
}
.right-enter-active {
transition: 1s;
}
.right-leave {
transform: translateX(0);
}
.right-leave-to {
transform: translateX(-100%);
}
.right-leave-active {
transition: 1s;
}
</style>
看效果,点击右边路由是right效果,点击左边路由是left效果
3.编程式的导航
之前我们都是用<router-link>来定义导航链接,我们也可以利用借助于 router 的实例方法,通过编写代码来实现导航的切换,看下router 的实例有哪些方法:
push 导航到不同url,向 history 栈添加一个新的记录
replace 导航到不同url,替换 history 栈中当前记录
back 回退一步
forward 前进一步
go 指定前进回退步数
添加3个button按钮
<button @click="click">跳转到help页面</button>
<button @click="back">后退一步</button>
<button @click="forward">前进一步</button>
借助路由的方法来实现跳转,前进,后退功能
methods: {
click() {
// this.$router.push({path:'/help'})
this.$router.push("/help");
},
back() {
//等价于 this.$router.go(-1); 负数为后退几步
this.$router.back();
},
forward() {
//等价于 this.$router.go(1);正数数为前进几步
this.$router.forward();
},
}
push 和replace 都可以导航都不同的url上 ,唯一的不同就是,replace 它不会向 history 添加新记录,而是跟它的方法名一样替换掉当前的 history 记录,看下面效果!
4.导航守卫
所谓导航守卫就是导航钩子函数,导航钩子函数有什么用,当导航发生变化时,导航钩子主要用来拦截导航,让它完成跳转或取消,有点类似vue的生命周期的钩子函数,在不同的时期可以做不同的操作,下面来看下有哪几种导航钩子函数!
4.1 全局守卫
全局就会想到全局变量,就是每个函数都可以用的到变量,那么全局守卫也就是每个路由导航都可以触发的钩子函数呗,下面来设置一个全局前置守卫
router.beforeEach((to,from,next)=>{
console.log(1);
next();
})
那么每次当一个导航触发时,都会打印1,看下面结果
to是将要进入的目标路由对象,from是当前导航正要离开的路由,next是个函数,只有调用这个next()才会进入下个导航,next(false)或者不写就会中断当前的导航,当然你可以自定义 路径next('/help'),写法和< router-link to='/help'>类似,也可以传入 next 的参数是一个 Error 实例,看下面代码
router.beforeEach((to,from,next)=>{
console.log(1);
next(new Error('导航错误'));
})
router.onError((error)=>{
console.log(error);
})
点击每个导航都不起作用,注意看右边打印的错误
全局前置守卫用的比较多的就是验证信息,好比一个登录验证,那些页面需要验证,哪些不需要验证,来做个最简单的模拟,我们在路由元信息里面加个isLogin来判断首次进入是否需要登录,比如about,user需要验证,其他页面不需要验证
let router = new Router({
linkActiveClass: "active",
mode: 'history',
routes: [{
path: '/',
component: Home,
},
{
path: '/home',
name: 'home',
components: {
default: Home,
sidebar: Sidebar
}
},
{
path: '/about',
component: About,
children: [{
path: '',
name: 'work',
component: Work,
meta: {
isLogin: true
},
},
{
path: 'company',
name: 'company',
component: Company,
},
{
path: 'contactUs',
name: 'contactUs',
component: ContactUs,
}
]
},
{
path: '/help',
name: 'help',
alias: "/123",
component: Help,
},
{
path: '/user/:id?',
name: 'user',
component: User,
meta: {
isLogin: true
},
},
{
path: '/login',
name: 'login',
component: Login
}
]
});
router.beforeEach((to, from, next) => {
if (to.meta.isLogin) {
next('/login');
} else {
next();
}
})
运行下代码,当点击到about,user会跳到登录页面,看下面效果
有全局前置守卫是不是应该也有全局后置守卫,和前置守卫不同的是少了一个next参数,所以它不会改变导航本身,那么这个全局后置守卫又有什么用呢?举个例子,当路由跳转了,导航改变了,是不是页面的tilt也应该相应的改变把,下面我们也来试试下,还是在元信息里面去设置title
let router = new Router({
linkActiveClass: "active",
mode: 'history',
routes: [{
path: '/',
component: Home,
meta: {
title: "Home"
}
},
{
path: '/home',
name: 'home',
components: {
default: Home,
sidebar: Sidebar
}
},
{
path: '/about',
component: About,
children: [{
path: '',
name: 'work',
component: Work,
meta: {
title: "About"
}
},
{
path: 'company',
name: 'company',
component: Company,
},
{
path: 'contactUs',
name: 'contactUs',
component: ContactUs,
}
]
},
{
path: '/help',
name: 'help',
alias: "/123",
component: Help,
meta: {
title: "Help"
}
},
{
path: '/user/:id?',
name: 'user',
component: User,
meta: {
title: "User"
}
}
]
});
router.afterEach((to, from) => {
if (to.meta.title) {
window.document.title = to.meta.title;
}
})
看下面执行效果,仔细看页面顶部的tile的变化
有了全局前置后置守卫,其实还有一个全局解析守卫( 2.5.0+),它在夹在全局前置和全局后置之间,和前置守卫类似(参数一样),区别是在导航被确认之前,同时在所有组件内守卫和异步路由组件被解析之后,解析守卫就被调用。下面会说到什么是组件内的守卫下面会说,意思是这个全局解析守卫会在异步路由被解析,路由被解析了那么路由里面还有组件,路由里面组件守卫也需要被调用之后,这个全局解析守卫才会被调用,现在说可能有点模糊,后面会说到整个执行过程!
4.2 路由独享的守卫
上面是全局的钩子函数,针对所有的路由,那么这个是单个路由的钩子函数,就只针对某个路由有效,很好理解!我们现在单独对use路由进行登录验证,当跳到use路由需要登录验证
{
path: '/user/:id?',
name: 'user',
component: User,
beforeEnter:(to, from,next)=>{
if(to.path=="/user"){
router.push('/login');
}
}
}
因为这个钩子函数只针对单个路由的钩子函数,所以你切换其他的路由是不会触发这个钩子函数的,看下面效果
4.3组件内的守卫
最后说到这个组件内的守卫,vue也有类似的钩子函数 beforeCreate,created,beforeMount,mounted ,beforeUpdate ,updated,beforeDestroy,destroyed。路由组件也有3个钩子函数,beforeRouteEnter,beforeRouteUpdate,beforeRouteLeave我们来一个个的看下有什么作用,先说beforeRouteEnter,看下面代码
beforeRouteEnter(to, from, next) {
console.log("beforeRouteEnter");
next();
},
beforeCreate() {
console.log("beforecreated");
},
created() {
console.log("created");
},
beforeMount() {
console.log("beforeMount");
},
mounted() {
console.log("mounted ");
},
看下执行顺序,beforeRouteEnter居然比beforeCreate还早执行, 在执行beforeCreate时,实例组件刚开始创建,元素dom和数据都还没有初始化
所以在路由beforeRouteEnter钩子函数中组件this实例是undefined,但是我们也可以通过next回调函数来获取组件实例
beforeRouteEnter(to, from, next) {
next((vm)=>{
console.log(vm.$route);
});
}
打印出组件实例的路由对象如下
在当前路由改变,如果是同一个组件,那么组件里面生命周期函数是不会再次调用的,那么这时候就用到beforeRouteUpdate这个钩子函数,之前做user组件时候用过,其实user/123,user/345,user/456用的是一个组件(只是展示数据不一样,功能还是一样的),每组信息都有个唯一的id,我们可以通过id不同来渲染不用的用户信息,因为三个路由都渲染同个组件,所以组件的生命周期钩子created函数不会再被调用,这时候我们可以用watch来监听路由的变化或者调用beforeRouteUpdate钩子函数,还是来看下之前的代码
let data = [
{
id: "123",
name: "张三",
sex: "男",
age: 18,
sprot: "running",
skill: "javascript",
},
{
id: "345",
name: "李四",
sex: "男",
age: 28,
sprot: "footabll",
skill: "java",
},
{
id: "456",
name: "小丽",
sex: "女",
age: 20,
sprot: "swimming",
skill: "node.js",
},
];
export default {
name: "User",
data() {
return {
userList: data,
user: {},
};
},
beforeRouteUpdate(to, from, next) {
//获取当前路由的动态id,然后找到对应的数据来展示
this.getUser(to.params.id);
next();
},
created() {
this.getUser(this.$route.params.id);
},
methods: {
getUser(id) {
let item = this.userList.find((us) => {
return us.id == id;
});
this.user = item;
},
},
这个钩子函数可以访问到组件实例,下图为当点击不用的用户展示出不用的数据效果
最后来说下beforeRouteLeave这个钩子函数,字面意思也很明显,路由离开的时候调用的钩子函数,我们可以通过这个函数来判断当前路由是否可以离开(比如数据没保存,或者数据没填写完整),比如当切换到user导航,只有点击了保存按钮才能离开,要不然不让离开,我们来模拟下!
<button @click="clickSave">保存</button>
export default {
name: "User",
data() {
return {
userList: data,
user: {},
save:false
};
},
beforeRouteUpdate(to, from, next) {
//获取当前路由的动态id,然后找到对应的数据来展示
this.getUser(to.params.id);
next();
},
beforeRouteLeave (to, from, next){
if(this.save){
next();
}else{
next(false)
}
},
created() {
this.getUser(this.$route.params.id);
},
methods: {
getUser(id) {
let item = this.userList.find((us) => {
return us.id == id;
});
this.user = item;
},
clickSave(){
this.save=true;
}
}
};
看下面执行效果
4.4导航解析流程
现在我们来模拟下两个场景,一个导航切换到另外一个导航(use页面切换到help页面),我们把测试的导航守卫钩子函数都加上,首先在index.js加上全局的钩子函数(beforeEach,beforeResolve,afterEach),use和help路由也分别加上路由独享的守卫钩子函数(beforeEnter )
import Vue from 'vue'
import Router from 'vue-router'
import About from '@/components/About'
import Help from '@/components/Help'
import Home from '@/components/Home'
import Work from '@/components/Work'
import Company from '@/components/Company'
import ContactUs from '@/components/ContactUs'
import Sidebar from '@/components/Sidebar'
import User from '@/components/User'
import Login from '@/components/Login'
Vue.use(Router)
let router = new Router({
linkActiveClass: "active",
mode: 'history',
routes: [{
path: '/',
component: Home,
},
{
path: '/about',
component: About,
children: [{
path: '',
name: 'work',
component: Work,
},
{
path: 'company',
name: 'company',
component: Company,
},
{
path: 'contactUs',
name: 'contactUs',
component: ContactUs,
}
]
},
{
path: '/help',
name: 'help',
alias: "/123",
component: Help,
beforeEnter: (to, from, next) => {
console.log("beforeEnter");
next();
},
},
{
path: '/user/:id?',
name: 'user',
component: User,
beforeEnter: (to, from, next) => {
console.log("beforeEnter");
next();
},
}
]
});
router.beforeEach((to, from, next)=>{
console.log("beforeEach");
next();
})
router.beforeResolve((to, from, next)=>{
console.log("beforeResolve");
next();
})
router.afterEach((to, from) => {
console.log("afterEach")
})
export default router
然后再help组件和use组件里面分别加上组件内的守卫钩子函数(beforeRouteEnter,beforeRouteUpdate ,beforeRouteLeave)
export default {
name: "Help",
beforeRouteEnter(to, from, next) {
console.log("beforeRouteEnter");
next(() => {
console.log("next回调");
});
},
beforeRouteUpdate(to, from, next) {
console.log("beforeRouteUpdate");
next();
},
beforeRouteLeave(to, from, next) {
console.log("beforeRouteLeave");
next();
},
};
当从use导航切换到help导航,注意右边的钩子函数的顺序,先是user组件的beforeRouteLeave==>全局的beforeEach==>help路由的beforeEnter==>help组件的beforeRouteEnter==>全局的beforeResolve==>全局的afterEach==>help组件beforeRouteEnter的next回调
当然如果你是从直接刷新help导航,而不是从use切换过来的,那么就不会有user组件的beforeRouteLeave,然后help导航切换到use导航也是一样的
当切换use里面的组件(动态路由匹配的,共用一个组件),这里组件里面只有beforeRouteUpdate会被调用,其他的钩子函数不会被调用
所以对于一个带有动态参数的路径之间跳转的时候,由于会渲染同样的组件,它的钩子执行顺序为:全局的beforeEach==>use组件的beforeRouteEnter==>全局的beforeResolve==>全局的afterEach,所以我们可以得出个总结(网上搬运的,省的自己画了)
所以完整的导航解析流程是:
导航被触发。
在失活的组件里调用 beforeRouteLeave 守卫。
调用全局的 beforeEach 守卫。
在重用的组件里调用 beforeRouteUpdate 守卫 (2.2+)。
在路由配置里调用 beforeEnter。
解析异步路由组件。
在被激活的组件里调用 beforeRouteEnter。
调用全局的 beforeResolve 守卫 (2.5+)。
导航被确认。
调用全局的 afterEach 钩子。
触发 DOM 更新。
调用 beforeRouteEnter 守卫中传给 next 的回调函数,创建好的组件实例会作为回调函数的参数传入