我们学VUE2的时候就接触过生命周期,它指的是组件从创建到销毁不同阶段的钩子函数,VUE3自然也会有生命周期。其实项目中也经常会用到VUE2的生命周期函钩子数,而且VUE2的生命周期和VUE3也有相同之处,所以在讲VUE3的生命周期之前我们先来快速回顾一下VUE2的生命周期。
VUE2的生命周期
VUE2的组件从创建到销毁一共会有四个阶段:创建=》挂载=》更新=》销毁,每一个阶段又分为两个部分,分别是创建前,创建完成,挂载前,挂载完成,更新前,更新完成,销毁前,销毁完成,所以正常来说一个组件的生命周期一共会有八个生命周期函数(不考虑路由守卫),也称之为钩子函数,每一个钩子函数都是一个API,和data,methods一样。
创建前(beforeCreate): 是在组件实例实例化以后,但是还没有初始化选项式API,也就是说还没有初始化data,methods,watch等,所以获取不到数据和方法,这也是生命周期里的第一个钩子函数。
创建完成(created): 此时已经初始化完选项式API了,可以获取到data的数据,methods的方法,watch的监视器等,此时可以进行一些数据操作,但是还没有挂载到DOM上面,获取不到DOM结构,比如ref属性标记的DOM结构,自然也是不能进行一些DOM操作的。
挂载前(beforeMount): 此时还没有挂载完成,所以还是不能进行一些DOM操作,一般不怎么用,如果要深入了解VUE2的原理,比如diff算法,那倒是可以了解下。
挂载完成(mounted): 此时已经讲实例挂载到DOM上了,可以获取到DOM结构了,这个阶段可以做一些DOM操作。
更新前(beforeUpdate): 当组件中的数据变化的时候,会执行这个钩子函数,此时数据是新的,但是还没有更新到页面上面去,所以此时页面和数据是不同步的。
更新完成(updated): 此时页面也更新完成,页面和组件数据保持一致,此时可以调用一些方法,比如表格的doLayout方法让表格重新排版。
销毁前(beforeDestory): 组件销毁之前调用的钩子函数,此时data,methods等还处于可用状态。
销毁完成(destoryed): 组件销毁完成以后调用的钩子函数。
额外说一点,就是当组件中有子组件的时候,这两个组件的生命周期函数的顺序(一般情况下):
父beforeCreate =》 父created =》 父beforeMounted =》 子beforeCreate =》 子created =》 子 beforeMounted =》 子mounted =》 父mounted =》 父beforeUpdate =》 子beforUpdate =》 子updated =》 父updated =》 父beforeDestory =》 子beforeDestory =》 子destoryed =》 父destoryed
VUE3的生命周期
VUE3的生命周期也有四个阶段,每个阶段也分完成前和完成后两部分,每一部分每一阶段的作用跟VUE2是一样的,不同的是调用的方式不一样了,名字也不一样了,而且VUE3不用写beforeCreate和created钩子函数了。
为什么不用写这两个钩子函数,我们想一想,这两个钩子函数是不是在组件一创建的时候就直接调用了,而且created钩子函数就能直接获取到数据和方法了,我们刚开始在讲setup的时候是不是也是如此,组件一创建就直接调用,而且里面可以定义数据和方法,所以这一点不是跟setup是一样的了吗,不明白的去看setup的帖子。所以VUE3就不会再写beforeCreate和created钩子函数了。
然后beforeMounted,mounted,beforeUpdate,updated钩子函数都前面加一个on,然后用驼峰写法,所以就是onBeforeMounted,onMounted,onBeforeUpdate,onUpdated,然后beforeDestory,destoryed销毁前和销毁完成钩子函数改为卸载前和卸载完成钩子函数也就是onBeforeUnMount和onUnMounted。
其实也大差不差,用的时候呢自然也要先引入,然后往每个钩子函数里面传一个函数参数,等到该阶段的时候就会直接调用这个函数参数。
因为要展示销毁过程,所以用父组件嵌套子组件,然后在父组件里去销毁子组件。
父组件代码:
<template>
<button @click="unMountHandle">卸载Test组件</button>
<Test v-if="flag"></Test>
</template>
<script lang="ts" setup>
import Test from "./views/Test.vue";
import { ref } from "vue";
let flag = ref(true);
function unMountHandle() {
flag.value = false;
}
</script>
子组件代码:
<template>
<div class="screen-view">
{{ num }}
<br />
<button @click="addHandle">点击+1</button>
</div>
</template>
<script lang="ts" setup>
import {
ref,
onBeforeMount,
onMounted,
onBeforeUpdate,
onUpdated,
onBeforeUnmount,
onUnmounted,
} from "vue";
let num = ref(0);
function addHandle() {
num.value++;
}
// 不写beforeCreate和created钩子函数,直接用setup代替了
console.log("创建");
// 挂载前 这种钩子函数都是往里面传一个函数参数,等到该阶段的时候就会调用这个函数参数
onBeforeMount(() => {
console.log("挂载前");
});
// 挂载完成
onMounted(() => {
console.log("挂载完成");
});
// 更新前
onBeforeUpdate(() => {
console.log("更新前");
});
// 更新完成
onUpdated(() => {
console.log("更新完成");
});
// 卸载前
onBeforeUnmount(() => {
console.log("卸载前");
});
// 卸载完成
onUnmounted(() => {
console.log("卸载完成");
});
</script>
加载界面如下:
可以看到挂载前的钩子函数和挂载完成的钩子函数都是调用的了,不知道会不会有人说这是因为我挂载前的代码写在挂载完成的代码前面,按照JS的执行顺序所以先打印挂载前然后再打印挂载完成,其实不然,修改下顺序也还是如此。
然后我们点击按钮更新下数据,会发现更新前钩子函数和更新完成钩子函数也是正常调用的。
然后销毁一下这个子组件,卸载前钩子函数和卸载完成钩子函数也是正常调用。
然后我们验证一下VUE3的父子组件的生命周期顺序是怎样的。
数据更新通过父子组件传值来完成。
父组件代码:
<template>
<button @click="unMountHandle">卸载Test组件</button>
<button @click="addHandle">点击+1</button>
<Test v-if="flag" :num="num"></Test>
</template>
<script lang="ts" setup>
import Test from "./views/Test.vue";
import {
ref,
onBeforeMount,
onMounted,
onBeforeUpdate,
onUpdated,
onBeforeUnmount,
onUnmounted,
} from "vue";
let num = ref(0);
let flag = ref(true);
function unMountHandle() {
flag.value = false;
}
function addHandle() {
num.value++;
}
console.log("父--创建");
onBeforeMount(() => {
console.log("父--挂载前");
});
onMounted(() => {
console.log("父--挂载完成");
});
onBeforeUpdate(() => {
console.log("父--更新前");
});
onUpdated(() => {
console.log("父--更新完成");
});
onBeforeUnmount(() => {
console.log("父--卸载前");
});
onUnmounted(() => {
console.log("父--卸载完成");
});
</script>
子组件代码:
<template>
<div class="screen-view">
{{ num }}
<br />
</div>
</template>
<script lang="ts" setup>
import {
onBeforeMount,
onMounted,
onBeforeUpdate,
onUpdated,
onBeforeUnmount,
onUnmounted,
} from "vue";
defineProps(["num"]);
console.log("子--创建");
onBeforeMount(() => {
console.log("子--挂载前");
});
onMounted(() => {
console.log("子--挂载完成");
});
onBeforeUpdate(() => {
console.log("子--更新前");
});
onUpdated(() => {
console.log("子--更新完成");
});
onBeforeUnmount(() => {
console.log("子--卸载前");
});
onUnmounted(() => {
console.log("子--卸载完成");
});
</script>
加载界面如下:
会发现这一点其实是跟VUE2也一样的,然后我们更新一下数据。
也跟VUE2一样,那我们再销毁一下这个组件。
这里会发现有一点不一样了,其实也还是一样的,只不过这里是卸载子组件没有卸载父组件而已,而且会发现这里父组件更新了一下,也是因为子组件里面有数据,卸载子组件的时候父组件会调用一下更新的钩子函数。
总而言之,VUE3的生命周期函数其实跟VUE2也差不多,只是名字会有一点变化,然后调用方式也有了一点变化,其次就是beforeCreate和created钩子函数不用写了,被setup本身所取代了。