vue常用的生命周期函数的用法与特点

426 阅读9分钟

今天我们继续来进阶vue,来聊一聊vue中一些常用的生命周期函数的用法与特点。

1. 什么是生命周期

首先,我们来搞清楚一下什么是生命周期。就我们人类来说,生命周期指的就是我们从出生到离世的整个过程。那放在vue中,生命周期指的就是页面(也可以说是组件,vue中的页面就是配了路由的组件嘛)从无到有、再到无的一个过程。

那vue中的页面从无到有经历了一个什么样的过程呢?我们知道,浏览器只读得懂html、css和js文件的代码,而在vue中,我们的代码是写在vue文件中的。所以,第一步,我们写在vue文件中的代码被vue的源代码读到后先去编译,编译成浏览器读得懂的代码;然后第二步,去挂载,我们知道,vue是单页应用,整个项目组只有一个html文件,我们想让我们写的组件能出现在页面上,就得挂载到那个唯一的html文件里;然后第三步,浏览器就把html文件中的代码渲染成一个页面去展示。

所以,一个页面的产生大概就是这样一个过程:

PixPin_2024-12-28_14-03-30.png

了解了这一点,我们就来先聊一聊今天要介绍的前两个生命周期函数。

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!"才对。我们来看一下:

image.png

我们发现,"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看看是否有值。

image.png

你看,是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向一个假接口请求数据,是可以拿到的。

image.png

所以在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文档中。我们来看一下:

image.png

哎,又获取到了,为什么?这是因为发送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函数就会触发。

PixPin_2024-12-28_22-15-46.gif

你看,当我反复点击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>

image.png

是有的。那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>

我在子组件里也写了两个挂载前挂载后的函数,此时页面上也会有这两条语句输出:

image.png

我再把App组件中的两个挂载前挂载后的输出语句放开,你来看一下输出结果:

image.png

输出结果是:先输出父组件挂载前,然后子组件挂载前,然后子组件挂载后,然后父组件挂载后。

你知道这是为什么吗?这应该很好想通吧,因为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这两个函数就会触发。

PixPin_2024-12-28_23-01-47.gif

你看,输出了卸载前和卸载后,这两个函数就触发了。

除了v-if能让一个组件消失,v-show是不是也行,那它能触发这两个函数吗,我们来试一下:

PixPin_2024-12-28_23-07-46.gif

我们发现是不能的,为什么呢?我们知道,v-show是不是去设置一个标签的display属性的呀,当v-show的值为false时,这个标签的display属性就为none,所以它并不是真的让这个组件卸载掉了,只是隐藏起来了,所以不会触发。

5. 总结

今天我们学习了一下vue中的六个生命周期函数:

它们分别有以下这些特征。

  1. onBeforeMount() 组件挂载之前
  2. onMounted() 组件挂载之后 ---- 获取dom结构
  3. onBeforeUpdate() 组件更新之前
  4. onUpdated() 组件更新之后
  5. ononBeforeUnmount() 组件卸载之前
  6. onUnmounted() 组件卸载之后

如果本篇文章对你有帮助的话请点个赞吧!