今天我们继续来进阶vue,来聊一聊vue中一些常用的生命周期函数的用法与特点。
1. 什么是生命周期
首先,我们来搞清楚一下什么是生命周期。就我们人类来说,生命周期指的就是我们从出生到离世的整个过程。那放在vue中,生命周期指的就是页面(也可以说是组件,vue中的页面就是配了路由的组件嘛)从无到有、再到无的一个过程。
那vue中的页面从无到有经历了一个什么样的过程呢?我们知道,浏览器只读得懂html、css和js文件的代码,而在vue中,我们的代码是写在vue文件中的。所以,第一步,我们写在vue文件中的代码被vue的源代码读到后先去编译,编译成浏览器读得懂的代码;然后第二步,去挂载,我们知道,vue是单页应用,整个项目组只有一个html文件,我们想让我们写的组件能出现在页面上,就得挂载到那个唯一的html文件里;然后第三步,浏览器就把html文件中的代码渲染成一个页面去展示。
所以,一个页面的产生大概就是这样一个过程:
了解了这一点,我们就来先聊一聊今天要介绍的前两个生命周期函数。
2. onBeforeMount,onMounted
onBeforeMount指的是组件挂载之前,onMounted指的是组件挂载之后。意思就是onBeforeMount函数会在组件挂载之前触发,onMounted函数会在组件挂载之后触发。
那我们来这样写:
<script>
import { onMounted, onBeforeMount } from 'vue'
export default {
setup() { // 入口函数
onMounted(() => {
console.log("mounted!");
})
onBeforeMount(() => {
console.log("beforeMount!");
})
return {
}
}
}
</script>
那你说这两条输出语句谁会先打印?按理来说我们是先调用了onMounted函数再调用了onBeforeMount函数,代码是从上往下执行的,应该先输出"mounted!"才对。我们来看一下:
我们发现,"beforeMount!"先输出。原因我们其实已经知道了,因为onBeforeMount函数是在组件挂载之前调用的,onMounted函数是在组件挂载之后调用的,所以肯定是"beforeMount!"先输出。
那你说,我现在想要获取dom结构,在哪个函数里面可以获取?onBeforeMount函数可不可以获取?我们知道onBeforeMount函数是发生在挂载之前的吧,那你看着那张图,在挂载之前那些容器div代码片段是不是已经生成了,变成浏览器能读得懂的代码了,那我们能不能获取到一个容器呢?是不可以的,如果我们想获取一个dom结构,是不是要document点什么什么去获取呀,而只有出现在html文件中的代码才叫document文档,才能够获取到,而onBeforeMount函数是在组件挂载到html文件之前触发的,所以不能获取。
而onMounted函数能不能获取dom结构?那就可以了,因为它是在组件挂载之后触发的,此时组件已经在document文档中了,就能获取到。
我们可以来看一下是不是这样:
<template>
<div id="box" ref="boxRef">
</div>
</template>
<script>
import { onMounted, onBeforeMount, ref } from 'vue'
export default {
setup() { // 入口函数
const boxRef = ref(null)
onMounted(() => {
console.log("mounted!", boxRef.value)
})
onBeforeMount(() => {
console.log("beforeMount!", boxRef.value)
})
return {
boxRef
}
}
}
</script>
vue中提供了一种可以获取dom结构的办法,我们就不用原生js获取了。我们给id为box的盒子绑定一个属性ref,值为我们定义的一个响应式变量boxRef,这样我们就能获取到这个dom结构。我们可以分别在两个函数中打印输出一下boxRef.value看看是否有值。
你看,是null,确实拿不到。
那再来,我们现在要发送一个http请求,向后端的一个接口请求数据,我们这段代码能写在哪个生命周期函数里?你说发送http请求和获取dom结构有关吗?是不是可以写在挂载之前啊,我们来试一下。
<template>
<div id="box" ref="boxRef">
</div>
</template>
<script>
import { onMounted, onBeforeMount, ref } from 'vue'
import axios from 'axios'
export default {
setup() { // 入口函数
const boxRef = ref(null)
onMounted(() => {
//console.log("mounted!", boxRef.value)
})
onBeforeMount(() => {
//console.log("beforeMount!", boxRef.value)
//发请求
axios.get('https://mock.mengxuegu.com/mock/66585c4db462b81cb3916d3e/songer/songer#!method=get')
.then(res => {
console.log(res)
})
})
return {
boxRef
}
}
}
</script>
我们使用axios向一个假接口请求数据,是可以拿到的。
所以在onBeforeMount函数中写http请求是ok的,那在onMounted肯定也是可以的。
那再来,我这样写:
<template>
<div id="box" ref="boxRef">
</div>
</template>
<script>
import { onMounted, onBeforeMount, ref } from 'vue'
import axios from 'axios'
export default {
setup() { // 入口函数
const boxRef = ref(null)
onMounted(() => {
//console.log("mounted!", boxRef.value)
})
onBeforeMount(() => {
//console.log("beforeMount!", boxRef.value)
//发请求
axios.get('https://mock.mengxuegu.com/mock/66585c4db462b81cb3916d3e/songer/songer#!method=get')
.then(res => {
console.log(boxRef.value)
})
})
return {
boxRef
}
}
}
</script>
我在onBeforeMount函数的接口请求代码里去获取dom结构,你说能获取到吗?哎,你刚刚不是聊过了吗,说onBeforeMount函数里获取不到dom结构,因为它是发生在挂载前,代码还没有出现在document文档中。我们来看一下:
哎,又获取到了,为什么?这是因为发送http请求是要耗时的对吧,假如你要耗时1秒,那我是不是得1秒之后才能执行那条输出语句,那1秒之后组件早就挂载完成了,v8引擎性能是很高的,很快就能完成挂载。所以此时代码就在document文档里了,所以能获取到。
3. onBeforeUpdate,onUpdated
我们再来聊俩个生命周期函数。onBeforeUpdate指的是组件更新之前,onUpdated指的是组件更新之后。意思就是onBeforeUpdate函数会在组件更新之前触发,onUpdated函数会在组件更新之后触发。
那什么叫组件更新呢?我们知道,当我们设置一个变量为响应式时,只要这个变量发生变化,页面就能重新渲染,这就叫组件更新,因为重新执行了一遍组件中的代码导致了页面更新嘛,所以我们来看一下:
<template>
<div id="box" ref="boxRef">
<button @click="add">Add - {{ count }}</button>
</div>
</template>
<script>
import { onMounted, onBeforeMount, ref, onBeforeUpdate, onUpdated } from 'vue'
import axios from 'axios'
export default {
setup() { // 入口函数
const boxRef = ref(null)
const count = ref(0)
onMounted(() => {
//console.log("mounted!", boxRef.value)
})
onBeforeMount(() => {
//console.log("beforeMount!", boxRef.value)
})
onBeforeUpdate(() => {
console.log("beforeUpdate!")
})
onUpdated(() => {
console.log('onUpdated');
})
const add = () => {
count.value++
}
return {
boxRef,
count,
add
}
}
}
</script>
我在页面上添加了一个button按钮,并给它绑定了一个点击事件,当我们点击它时,count就会加1,因为count是响应式的,所以它变了页面就会重新渲染。那onBeforeUpdate函数和onUpdated函数就会触发。
你看,当我反复点击button按钮时,onBeforeUpdate函数和onUpdated函数就会反复触发,而且是onBeforeUpdate函数先触发,因为count的变化导致了组件更新。
所以,这两个生命周期函数是只要组件中有变量发生更新,不管是谁,它们都会执行。这里还有个小细节,是只有放在组件中的变量更新了,这两个函数才会执行。意思就是如果我把count从组件中拿掉,然后去点击button按钮,count的值是不是就一定更新了,但因为它现在没有出现在组件中,就不会导致组件更新,这两个函数就不会执行了。
4. ononBeforeUnmount,onUnmounted
我们再来聊今天最后两个生命周期函数。ononBeforeUnmount指的是组件卸载之前,onUnmounted指的是组件卸载之后。
那组件卸载指的又是什么?我们来看。
我再创建一个Child.vue文件,然后把它引入到App.vue组件中作为它的子组件。我在Child组件里放了一个Child单词,那现在Child应该就能出现在页面上了。
<template>
<div id="box" ref="boxRef">
<button @click="add">Add - {{ count }}</button>
<Child></Child>
</div>
</template>
<script>
import { onMounted, onBeforeMount, ref, onBeforeUpdate, onUpdated } from 'vue'
import axios from 'axios'
import Child from './components/Child.vue'
export default {
components: {
Child
},
setup() { // 入口函数
const boxRef = ref(null)
const count = ref(0)
onMounted(() => {
//console.log("mounted!", boxRef.value)
})
onBeforeMount(() => {
//console.log("beforeMount!", boxRef.value)
})
onBeforeUpdate(() => {
//console.log("beforeUpdate!")
})
onUpdated(() => {
//console.log('onUpdated');
})
const add = () => {
count.value++
}
return {
boxRef,
count,
add
}
}
}
</script>
是有的。那Child组件是不是就挂载到App组件里了,那组件的卸载是指什么呢?把Child组件从App组件中移除掉就叫组件的卸载了。
我们在Child组件中这样写:
<template>
<div>
<h1>child</h1>
</div>
</template>
<script setup>
import { onBeforeMount, onMounted } from 'vue'
onBeforeMount(() => {
console.log('child beforeMount')
})
onMounted(() => {
console.log('child mounted')
})
</script>
<style lang="css" scoped></style>
我在子组件里也写了两个挂载前挂载后的函数,此时页面上也会有这两条语句输出:
我再把App组件中的两个挂载前挂载后的输出语句放开,你来看一下输出结果:
输出结果是:先输出父组件挂载前,然后子组件挂载前,然后子组件挂载后,然后父组件挂载后。
你知道这是为什么吗?这应该很好想通吧,因为Child组件是App组件的子组件,那肯定是父组件先进行挂载,然后输出挂载前,然后读到子组件的代码,就去进行子组件的挂载,子组件就输出挂载前,然后等到子组件挂载完了之后,输出子组件挂载后,父组件再接着读取下面的代码,再输出挂载后。
这是一个小细节,那再回到我们的主题上来,我在子组件里写上ononBeforeUnmount,onUnmounted两个函数。
<template>
<div>
<h1>child</h1>
</div>
</template>
<script setup>
import { onBeforeMount, onMounted, onBeforeUnmount, onUnmounted } from 'vue'
onBeforeMount(() => {
//console.log('child beforeMount')
})
onMounted(() => {
//console.log('child mounted')
})
onBeforeUnmount(() => {
console.log('child beforeUnmount 卸载前');
})
onUnmounted(() => {
console.log('child unmounted 卸载后')
})
</script>
<style lang="css" scoped></style>
那什么情况下这两个函数会触发呢?把Chid组件从App组件中移除掉就会触发。我们这么来干:
<template>
<div id="box" ref="boxRef">
<button @click="add">Add - {{ count }}</button>
<Child v-if="showChild"></Child>
<button @click="showChild = !showChild">hide child</button>
</div>
</template>
<script>
import { onMounted, onBeforeMount, ref, onBeforeUpdate, onUpdated } from 'vue'
import axios from 'axios'
import Child from './components/Child.vue'
export default {
components: {
Child
},
setup() { // 入口函数
const boxRef = ref(null)
const count = ref(0)
const showChild = ref(true)
onMounted(() => {
//console.log("mounted!", boxRef.value)
})
onBeforeMount(() => {
//console.log("beforeMount!", boxRef.value)
})
onBeforeUpdate(() => {
//console.log("beforeUpdate!")
})
onUpdated(() => {
//console.log('onUpdated');
})
const add = () => {
count.value++
}
return {
boxRef,
count,
add,
showChild
}
}
}
</script>
我们给子组件标签绑定一个v-if属性,让它的值为我们设置的一个变量showChild,当变量showChild的值为false时,这个标签就会从这个组件中消失,我们再添加一个button按钮,绑定一个点击事件,就让showChild每次都取反。这样当我们点击这个按钮时,Child组件就会从App组件中卸载,Child这个单词就看不见了,ononBeforeUnmount和onUnmounted这两个函数就会触发。
你看,输出了卸载前和卸载后,这两个函数就触发了。
除了v-if能让一个组件消失,v-show是不是也行,那它能触发这两个函数吗,我们来试一下:
我们发现是不能的,为什么呢?我们知道,v-show是不是去设置一个标签的display属性的呀,当v-show的值为false时,这个标签的display属性就为none,所以它并不是真的让这个组件卸载掉了,只是隐藏起来了,所以不会触发。
5. 总结
今天我们学习了一下vue中的六个生命周期函数:
它们分别有以下这些特征。
- onBeforeMount() 组件挂载之前
- onMounted() 组件挂载之后 ---- 获取dom结构
- onBeforeUpdate() 组件更新之前
- onUpdated() 组件更新之后
- ononBeforeUnmount() 组件卸载之前
- onUnmounted() 组件卸载之后
如果本篇文章对你有帮助的话请点个赞吧!