7_Vue组件生命周期

1. Vue组件生命周期

一个Vue组件就是一个Vue实例对象,当对象被实例化出来之后,要经过初始化数据、编译模板、挂载DOM->渲染、更新->渲染、卸载等一系列过程,这个过程就是组件的生命周期,各个阶段都有对应的事件钩子(实例方法的调用)。 前面章节中已经用到了 created和destroyed 这两个事件钩子,分别在创建后和销毁后调用。

2. 官方生命周期图示

参考文档: https://cn.vuejs.org/v2/guide/instance.html
图片来源于网络
在这里插入图片描述
红色方框就是事件钩子,它们其实是一个函数:

生命周期钩子组件状态说明
beforeCreate实例初始化之后,this指向创建的实例,不能访问到data、computed、watch、methods上的方法和数据常用于初始化非响应式变量
created实例创建完成,可访问data、computed、watch、methods上的方法和数据,未挂载到DOM,不能访问到el属性 ref属性内容为空数组常用于简单的ajax请求,页面的初始化
beforeMount在挂载开始之前被调用,beforeMount之前,会找到对应的template,并编译成render函数
mounted实例挂载到DOM上,此时可以通过DOM API获取到DOM节点,$ref属性可以访问常用于获取VNode信息和操作,ajax请求
beforeupdate响应式数据更新时调用,发生在虚拟DOM打补丁之前适合在更新之前访问现有的DOM,比如手动移除已添加的事件监听器
updated虚拟 DOM 重新渲染和打补丁之后调用,组件DOM已经更新,可执行依赖于DOM的操作避免在这个钩子函数中操作数据,可能陷入死循环
beforeDestroy实例销毁之前调用。这一步,实例仍然完全可用,this仍能获取到实例常用于销毁定时器、解绑全局事件、销毁插件对象等操作
destroyed实例销毁后调用,调用后,Vue 实例指示的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁
  1. created阶段的ajax请求与mounted请求的区别:前者页面视图未出现,如果请求信息过多,页面会长时间处于白屏状态

  2. mounted 不会承诺所有的子组件也都一起被挂载。如果你希望等到整个视图都渲染完毕,可以用 vm.$nextTick 后面会讲解详细用法

  3. vue2.0之后主动调用$destroy()不会移除dom节点,作者不推荐直接destroy这种做法,如果实在需要这样用可以在这个生命周期钩子中手动移除dom节点

3. 函数执行情况测试

编写示例观察钩子函数执行情况

src/components/App.vue


<template>
  <div>
    <button class="btn btn-warning" @click="msg='更新msg'">更新msg</button> [{{msg}}]
    <button class="btn btn-danger" @click="handleDestroy">手工调用销毁</button>
    <button class="btn btn-info" @click="userName='lisi'">更新userName</button> [{{userName}}]
  </div>
</template>

<script>
export default {
  name :'App',
  data(){
    return {
      msg: '默认消息'
    }
  },
  methods :{
    handleDestroy(){
      this.$destroy()
    }
  },

  beforeCreate() {
      this.userName ='zhangsan' //data中添加一个数据
      console.log(`访问data中的msg数据:${this.msg}--${this.userName}--beforeCreate`)
  },
  created() {
      console.log(`访问data中的msg数据:${this.msg}--${this.userName}--created`)
  },
  beforeMount() {
      console.log(`访问data中的msg数据:${this.msg}--${this.userName}--beforeMount`)
  },
  mounted() {
      console.log(`访问data中的msg数据:${this.msg}--${this.userName}--mounted`)
  },
  beforeUpdate() {
      console.log(`访问data中的msg数据:${this.msg}--${this.userName}--beforeUpdate`)
  },
  updated() {
      console.log(`访问data中的msg数据:${this.msg}--${this.userName}--updated`)
  },
  beforeDestroy() {
      console.log(`访问data中的msg数据:${this.msg}--${this.userName}--beforeDestroy`)
  },
  destroyed() {
      console.log(`访问data中的msg数据:${this.msg}--${this.userName}--destroyed`)
  },
  methods: {
      handleDestroy() {
          this.$destroy()
      }
  }
}
</script>

在这里插入图片描述
beforeCreate 中 无法访问data中的数据(method中的也一样),添加的userName 是非响应式变量,也就是说 更改了userName之后,不会重新渲染组件。

点击’更新msg’ 按钮后发现 beforeUpdate与 updated被调用了,页面数据也更新了,因为data中定义的都是响应式变量。

点击 ‘更新userName’ 按钮后,发现 beforeUpdate和 updated没有被调用了,页面数据也没有更新。因为beforeCreate 中添加的数据是非响应式数据
在这里插入图片描述

最后调用 ’手工调用销毁‘ 按钮,发现 beforeDestroy与 destroyed被调用了。
在这里插入图片描述
小结:

  • 初始化组件时,仅执行了beforeCreate/Created/beforeMount/mounted四个钩子函数
  • 当改变data中定义的变量(响应式变量)时,会执行beforeUpdate/updated钩子函数
  • 当切换组件(当前组件未缓存)时,会执行beforeDestory/destroyed钩子函数
  • 初始化和销毁时的生命钩子函数均只会执行一次,beforeUpdate/updated可多次执行

大体可以将Vue组件生命周期分成三个阶段,每个阶段对应的钩子函数如下:

  • 创建阶段

    beforeCreated, created, beforeMount,mounted

  • 运行阶段

    beforeUpdate, updated

  • 销毁阶段

    beforeDestroy, destroyed

4. 父子组件的生命周期

​ 在单一组件中,钩子的执行顺序是beforeCreate->created ->beforeMount-> mounted -> destroyed,但当父子组件嵌套时,父组件和子组件各拥有各自独立的钩子函数,这些父子组件的这些钩子是如何交融执行,且执行顺序又是怎样的呢?

​ Vue父子组件生命周期钩子的执行顺序遵循:从外到内,然后再从内到外,不管嵌套几层深,也遵循这个规律。

下面开始验证:App.vue 为父组件,Child.vue为子组件,在组件内部分别打印出生命周期中的8个钩子函数,然后运行:

App.vue

<template>
  <div>
    <button class="btn btn-danger" @click="handleDestroy">手工调用销毁</button>
    <hr />
    <child></child>
  </div>
</template>

<script>
import Child from './Child'

export default {
  name :'App',
  data(){
    return {
    }
  },
  methods :{
    handleDestroy(){
      this.$destroy()
    }
  },

  beforeCreate() {
      console.log(`App.vue[parent] --beforeCreate`)
  },
  created() {
      console.log(`App.vue[parent] --created`)
  },
  beforeMount() {
      console.log(`App.vue[parent] --beforeMount`)
  },
  mounted() {
      console.log(`App.vue[parent] --mounted`)
  },
  beforeUpdate() {
      console.log(`App.vue[parent] --beforeUpdate`)
  },
  updated() {
      console.log(`App.vue[parent]--updated`)
  },
  beforeDestroy() {
      console.log(`App.vue[parent]--beforeDestroy`)
  },
  destroyed() {
      console.log(`App.vue[parent]--destroyed`)
  },
  methods: {
      handleDestroy() {
          this.$destroy()
      }
  },
  components:{
    Child
  }
}
</script>

Child.vue

<template>
  <div class="size">这是一个子组件</div>
</template>

<script>
export default {
  beforeCreate() {
    console.log(`Child.vue[child] --beforeCreate`);
  },
  created() {
    console.log(`Child.vue[child] --created`);
  },
  beforeMount() {
    console.log(`Child.vue[child] --beforeMount`);
  },
  mounted() {
    console.log(`Child.vue[child] --mounted`);
  },
  beforeUpdate() {
    console.log(`Child.vue[child] --beforeUpdate`);
  },
  updated() {
    console.log(`Child.vue[child] --updated`);
  },
  beforeDestroy() {
    console.log(`Child.vue[child] --beforeDestroy`);
  },
  destroyed() {
    console.log(`Child.vue[child] --destroyed`);
  }
};
</script>

<style lang="scss" scoped>
.size {
  height: 100px;
  width: 100px;
  border: 1px solid red;
}
</style>

运行结果:
在这里插入图片描述
在这里插入图片描述
手工销毁父组件:
在这里插入图片描述

父组件先创建,然后子组件创建;子组件先挂载,然后父组件挂载。

在这里插入图片描述

删除父组件的时候,先调用父组件的 beforeDestroy,然后彻底删除子组件,最后调用父组件的destroyed方法

5. 理解 $el

官方API文档中 给出了一个组件(vue实例对象)的API ,可以看到一个vue实例对象中有 $el 属性。

我们知道一个 vue组件,有且仅有一个根元素,那么 $el就是这个 DOM根元素。注意它是一个原生的DOM节点。在8个生命周期钩子函数中分别打印出这个值我们发现:

在 beforeCreate,created,beforeMount 的钩子函数执行的时候,这个 $el 都是 undefined,

从 mounted 开始,它是 DOM节点对象。

这很容易理解,就是组件实例对象 创建之前(beforeCreate),实例已经创建(created),DOM挂载之前(beforeMount) 真正的节点此时还没有产生,所以是 undefined。 而一点节点产生之后,$el就指向了真正的DOM节点。

在前面的例子中,“手工调用销毁” 按钮被点击后,vue组件虽然被销毁了,但是 DOM节点依然没有删除。这是因为 vue2.0之后主动调用$destroy()不会移除dom节点,作者不推荐直接destroy这种做法。如果实际开发中确实要这样做,那么我们可以在 destroyed 钩子函数中获取到 $el对象,然后使用 自身的 remove方法进行移除

destroyed() {
    console.log(`Child.vue[child] --destroyed`);
    this.$el.remove()
}

这样在点击 “手工调用销毁” 按钮之后,节点就被移除掉了。

6. 理解 $nextTick() 方法

vue 实例对象API中 有一个 $nextTick()方法 , 官方API的解释为:

将回调延迟到下次 DOM 更新循环之后执行。在修改数据之后立即使用它,然后等待 DOM 更新。

那么如何理解这句话?

做一个简单的实验,步骤如下:

  1. 页面中放置一个div,div中有一段文本" 默认被隐藏的div "。这个div 使用 v-if 来控制它隐藏还是显示,初始状态为隐藏状态
  2. 使用按钮让 div显示还是隐藏
    在这里插入图片描述
    点击按钮后:
    在这里插入图片描述
    再次点击后,div被隐藏。

App.vue


<template>
  <div>
    <button @click="toggleDiv">{{show?'隐藏':'显示'}}div</button>
    <div id="mydiv" class="mydiv" v-if="show">默认被隐藏的div</div>
  </div>
</template>

<script>
export default {
  name :'App',
  data(){
    return {
      show: false
    }
  },
  methods:{
    toggleDiv(){
        this.show=!this.show
    }
  }
}
</script>
<style scoped>
    .mydiv{
      width:300px;
      height:100px;
      border:1px solid red;
    }
</style>

现在修改 toggleDiv函数,在div由隐藏状态变为显示状态的时候,同时获取 div中的文本。

toggleDiv(){
    const divDom=document.getElementById("mydiv")
    console.log(divDom,divDom.innerHTML)
    this.show=!this.show
}

结果是 :当点击 “显示” 的时候,此时使用 document.getElementById(“mydiv”) 获取到的DOM元素为 空。

原因是当 div处于隐藏状态的时候,此时 div这个节点是不存在的,当点击按钮,toggleDiv函数开始执行的时候,dom节点依然不存在,所以获取到的是空。

那将获取的代码放到 将 show设置为true之后呢?

toggleDiv(){
    this.show=!this.show
    const divDom=document.getElementById("mydiv")
    console.log(divDom,divDom.innerHTML)
}

点击按钮之后,div显示出来了,但 获取到的dom元素依然为空。这是为什么?这里就涉及到Vue的一个重要的概念:异步更新队列。

Vue在观察到数据变化时,并不是直接更新DOM,比如上面的 show 变量值改变了,它实际上并没有立即直接更新DOM让 div节点创建出来, 而是开启一个队列,并缓冲在同一事件循环中发生的所有数据更改,一个数据如果在同一个事件循环中被更改多次,那只有最后一次是最终的状态。在下一个事件循环tick中,Vue会应用改变,从而对DOM只做一次渲染。如果没有这种缓冲机制的话,DOM就会做很多次无用的重绘,从而增加系统开销。

$nextTick 这个方法就是用来让我们知道什么时候DOM更新完成了。 该方法的参数是一个回调函数。也就是说当Vue组件中的响应式数据发生变化,导致了DOM重绘,当重绘完毕后就会执行 $nextTick 参数指定的回调函数。需要注意的是:回调函数中的this指向的是vue组件对象

toggleDiv() {
    this.show = !this.show;
    this.$nextTick(() => {
        // 这个回调函数中的this指向的是 vue组件对象
        const divDom = document.getElementById("mydiv");
        console.log(divDom, divDom.innerHTML);
    });
}

​ 再次点击按钮的时候,就能够正常找到这个div元素节点了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

paopao_wu

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值