chaihongjun.me

Vue路由导航守卫和组件生命周期总结

Vue-router的钩子函数和组件生命周期的执行顺序总结。单独讨论Vue组件的生命周期会觉得非常的容易,但是一旦涉及到路由导航守卫,情况就变的复杂的多了,从技术原理来说无论是导航守卫还是组件生命周期都是函数,只是这些函数执行的时间不一,触发函数执行的条件也不一。

使用VueCli4搭建的初始模板,并做简单的修改:

Home组件:

<template>
  <div class="home">
    <img alt="Vue logo" src="../assets/logo.png" />
    <HelloWorld msg="Welcome to Your Vue.js App" />
  </div>
</template>
<script>
// @ is an alias to /src
import HelloWorld from "@/components/HelloWorld.vue";
export default {
  name: "Home",
  components: {
    HelloWorld,
  },
  methods: {
    randChange() {
      this.shuju = Math.floor(Math.random() * 10);
    },
  },
  beforeRouteEnter(to, from, next) {
    // 在渲染该组件的对应路由被 confirm 前调用
    // 不!能!获取组件实例 `this`
    // 因为当守卫执行前,组件实例还没被创建
    console.log("执行了 Home 组件  beforeRouteEnter");
    next();
  },
  beforeRouteUpdate(to, from, next) {
    // 在当前路由改变,但是该组件被复用时调用
    // 可以访问组件实例 `this`
    console.log("执行了 Home 组件  beforeRouteUpdate");
    next();
  },
  beforeRouteLeave(to, from, next) {
    // 导航离开该组件的对应路由时调用
    // 可以访问组件实例 `this`
    console.log("执行了 Home 组件  beforeRouteLeave");
    next();
  },
  //组件生命周期
  beforeCreate() {
    console.log("执行了 Home 组件  beforeCreate 生命周期");
  },
  created() {
    console.log("执行了 Home 组件 Created 生命周期");
  },
  beforeMount() {
    console.log("执行了 Home 组件  beforeMount 生命周期");
  },
  mounted() {
    console.log("执行了 Home 组件  mounted 生命周期");
  },
  beforeUpdate() {
    console.log("执行了 Home 组件  beforeUpdate 生命周期");
  },
  updated() {
    console.log("执行了 Home 组件  updated 生命周期");
  },
  beforeDestroy() {
    console.log("执行了 Home 组件  beforeDestroy 生命周期");
  },
  destroyed() {
    console.log("执行了 Home 组件  destroyed 生命周期");
  },
};
</script>

About组件:

<template>
  <div class="about">
    <h1>This is an about page</h1>
  </div>
</template>
<script>
export default {
  beforeRouteEnter(to, from, next) {
    // 在渲染该组件的对应路由被 confirm 前调用
    // 不!能!获取组件实例 `this`
    // 因为当守卫执行前,组件实例还没被创建
    console.log("执行了 About 组件  beforeRouteEnter");
    next();
  },
  beforeRouteUpdate(to, from, next) {
    // 在当前路由改变,但是该组件被复用时调用
    // 可以访问组件实例 `this`
    console.log("执行了 About 组件  beforeRouteUpdate");
    next();
  },
  beforeRouteLeave(to, from, next) {
    // 导航离开该组件的对应路由时调用
    // 可以访问组件实例 `this`
    console.log("执行了 About 组件  beforeRouteLeave");
    next();
  },
  //组件生命周期
  beforeCreate() {
    console.log("执行了 About 组件  beforeCreate 生命周期");
  },
  created() {
    console.log("执行了 About 组件 Created 生命周期");
  },
  beforeMount() {
    console.log("执行了 About 组件  beforeMount 生命周期");
  },
  mounted() {
    console.log("执行了 About 组件  mounted 生命周期");
  },
  beforeUpdate() {
    console.log("执行了 About 组件  beforeUpdate 生命周期");
  },
  updated() {
    console.log("执行了 About 组件  updated 生命周期");
  },
  beforeDestroy() {
    console.log("执行了 About 组件  beforeDestroy 生命周期");
  },
  destroyed() {
    console.log("执行了 About 组件  destroyed 生命周期");
  },
};
</script>

router/index.js 路由配置文件:

import Vue from "vue";
import VueRouter from "vue-router";
import Home from "../views/Home.vue";

Vue.use(VueRouter);

const routes = [
  {
    path: "/",
    name: "Home",
    component: Home,
    // 路由独享守卫
    beforeEnter: (to, from, next) => {
      console.log("执行了 Home组件 路由独享守卫 beforeEnter");
      next();
    },
  },
  {
    path: "/about",
    name: "About",
    // route level code-splitting
    // this generates a separate chunk (about.[hash].js) for this route
    // which is lazy-loaded when the route is visited.
    component: () =>
      import(/* webpackChunkName: "about" */ "../views/About.vue"),
    // 路由独享守卫
    beforeEnter: (to, from, next) => {
      console.log("执行了 About 组件 路由独享守卫 beforeEnter");
      next();
    },
  },
];

const router = new VueRouter({
  mode: "history",
  base: process.env.BASE_URL,
  routes,
});

//全局前置守卫
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;

启动项目,并切换到console看输出状态,然后再切换导航看控制台输出的内容。

假设从组件Home导航到组件About,则会经过以下几个路由的钩子函数和组件生命周期。会分为两种情况。

一 初始化第一个页面  

无论是SPA还是MPA都会有一个默认的初始化首页。这个初始化页面会按顺序经过如下各种钩子:


导航钩子/组件生命周期触发对象说明
1. 全局前置守卫 beforeEach(to, from, next)this.$router导航钩子全局路由级级别
2. Home组件 路由独享守卫 beforeEnter(to, from, next)this.$route导航钩子当前激活路由级别(进入)
3. Home 组件  beforeRouteEnter(to, from, next)Home 组件导航钩子当前激活组件级别(进入)
4. 全局解析守卫 beforeResolve(to, from, next)this.$router导航钩子全局路由级级别
5. 全局后置钩子 afterEach(to, from)this.$router导航钩子全局路由级级别
6. Home 组件  beforeCreate 生命周期Home 组件组件生命周期
7. Home 组件 Created 生命周期Home 组件组件生命周期
8. Home 组件  beforeMount 生命周期Home 组件组件生命周期
9. Home 组件  mounted 生命周期Home 组件组件生命周期

二 当从Home导航到About

导航钩子/组件生命周期触发对象说明
1. Home 组件  beforeRouteLeave(to, from, next)Home 组件导航钩子当前失活路由级别(离开)
2. 全局前置守卫 beforeEach(to, from, next)this.$router导航钩子全局路由级级别
3. About 组件 路由独享守卫 beforeEnter(to, from, next)this.$route导航钩子当前激活路由级别(进入)
4. About 组件  beforeRouteEnter(to, from, next)About 组件导航钩子当前激活组件级别(进入)
5. 全局解析守卫 beforeResolve(to, from, next)this.$router导航钩子全局路由级级别
6.  全局后置钩子 afterEach(to, from)this.$router导航钩子全局路由级级别
7. About 组件 beforeCreate 生命周期About 组件组件生命周期
8. About 组件 Created 生命周期About 组件组件生命周期
9. About 组件  beforeMount 生命周期About 组件组件生命周期
10. Home 组件  beforeDestroy 生命周期Home 组件组件生命周期
11. Home 组件  destroyed  生命周期Home 组件组件生命周期
12. About 组件  mounted 生命周期About 组件组件生命周期

总结规律:

  1. 整体执行规律,先执行的是导航守卫钩子,再执行组件自身的生命周期,完成全部的钩子函数。遇到组件切换的,则在新组件beforeMount后,mounted前,旧组件发生 beforeDestroydestroyed

  2. 如果有导航切换,则先触发的是失活组件的导航钩子beforeRouteLeave(to, from, next)**,再执行全局路由级别的前置导航钩子beforeEach(to, from, next),否则直接执行全局路由级别的前置钩子

  3. 接着是执行被激活的路由对象级别的钩子beforeEnter(to, from, next)

  4. 再是执行被激活的组件对象级别的钩子beforeRouteEnter(to, from, next)

  5. 全局路由级别的beforeResolve(to, from, next)afterEach(to, from)

  6. 最后是组件自身的生命周期

整理后的文档: https://chaihongjun.me/tools/vuehooks.html

知识共享许可协议本作品采用知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议进行许可。作者:柴宏俊»