Vue全家桶系列之Vue-router(五)

1.前言

上篇文章说了vue-router的路由命名视图,动态路由匹配,路由滚动行为,接下来再看看其他的用法

2.路由过渡效果

过渡效果类似css中的过滤,我们用的是transititon的封装组件来为路由添加过渡动画的,首先来看个图了解下有哪些过渡的css类名,Enter就是路由的进入,是从Opacity 0到1(淡入),Leave就是路由离开是从Opacity 1到0(淡出),v-enter-active,v-leave-active是活动的状态,可以被用来定义过渡的时间,延迟和曲线函数等
在这里插入图片描述

  1. v-enter: 定义进入过渡的开始状态
  2. v-enter-active: 定义进入活动状态
  3. v-enter-to: 定义进入的结束状态
  4. v-leave: 定义离开过渡的开始状态
  5. v-leave-active: 定义离开活动状态
  6. 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的过渡模式!

  1. out-in: 当前元素先进行过渡,完成之后新元素过渡进入
  2. 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 的实例有哪些方法:

  1. push 导航到不同url,向 history 栈添加一个新的记录
  2. replace 导航到不同url,替换 history 栈中当前记录
  3. back 回退一步
  4. forward 前进一步
  5. 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,所以我们可以得出个总结(网上搬运的,省的自己画了)
在这里插入图片描述
所以完整的导航解析流程是:

  1. 导航被触发。
  2. 在失活的组件里调用 beforeRouteLeave 守卫。
  3. 调用全局的 beforeEach 守卫。
  4. 在重用的组件里调用 beforeRouteUpdate 守卫 (2.2+)。
  5. 在路由配置里调用 beforeEnter。
  6. 解析异步路由组件。
  7. 在被激活的组件里调用 beforeRouteEnter。
  8. 调用全局的 beforeResolve 守卫 (2.5+)。
  9. 导航被确认。
  10. 调用全局的 afterEach 钩子。
  11. 触发 DOM 更新。
  12. 调用 beforeRouteEnter 守卫中传给 next 的回调函数,创建好的组件实例会作为回调函数的参数传入
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值