vue 父子组件生命周期的调用顺序

418 阅读5分钟

想起了一道曾经面试的时候,被面试官问到的问题:关于父子组件生命周期调用顺序是咋样的?当初懵懂,现在赶紧整理一下,以防在同一个坑上跌倒2次。

枚举几种情况下父子组件生命周期的调用顺序:

  • 开始渲染阶段
  • 父组件属性改变不会引起子组件变化的场景
  • 子组件属性发生变化且不会影响父组件的场景
  • 父组件属性改变引起子组件变化的场景
  • 父组件销毁时
  • v-if控制子组件的显隐的场景
  • v-show控制子组件的显隐的场景

现有父组件Parent,子组件Child:

界面如下:

代码:

Parent组件:


<template>
  <div class="main-layout">
    <div>
      parent name:
      <el-input v-model="name" style="width: 200px" />
    </div>
    <br />
    <el-button @click="addCount">增加数量</el-button>
    <br /><br />
    <el-button @click="childToggole">Child组件显隐(v-if)</el-button>
    <el-button @click="childToggole2">Child组件显隐(v-show)</el-button>
    <br /><br />

    <child :count="count" v-if="childShow" v-show="childShow2" />
  </div>
</template>
<script lang="ts">
import { Component, Vue } from "vue-property-decorator";
import Child from "./Child.vue";

@Component({
  name: "Parent",
  components: {
    Child
  }
})
export default class HelloWorld extends Vue {
  name = "头部";

  childShow = true;

  childShow2 = true;

  count = 0;

  private childToggole() {
    this.childShow = !this.childShow;
  }

  private childToggole2() {
    this.childShow2 = !this.childShow2;
  }

  private addCount() {
    this.count = this.count + 1;
  }

  beforeCreate() {
    console.log("Parent --> beforeCreate");
  }

  created() {
    console.log("Parent --> created");
  }

  beforeMount() {
    console.log("Parent --> beforeMount");
  }

  mounted() {
    console.log("Parent --> mounted");
  }

  beforeUpdate() {
    console.log("Parent --> beforeUpdate");
  }

  updated() {
    console.log("Parent --> updated");
  }

  beforeDestroy() {
    console.log("Parent --> beforeDestroy");
  }

  destroyed() {
    console.log("Parent --> destroyed");
  }
}
</script>

<style lang="less">
.main-layout {
  margin-top: 20px;
  background-color: #ddd;
  padding: 20px;
}
</style>

Child组件:

<template>
  <div class="child-layout">
    <div>child name: <el-input v-model="name" style="width: 100px" /></div>
    <br />
    <div>数量:{{ count }}</div>
  </div>
</template>
<script lang="ts">
import { Component, Vue, Prop } from "vue-property-decorator";

@Component({ name: "Child" })
export default class HelloWorld extends Vue {
  @Prop({ default: 0 }) count?: number;

  name = "";

  beforeCreate() {
    console.log("Child --> beforeCreate");
  }

  created() {
    console.log("Child --> created");
  }

  beforeMount() {
    console.log("Child --> beforeMount");
  }

  mounted() {
    console.log("Child --> mounted");
  }

  beforeUpdate() {
    console.log("Child --> beforeUpdate");
  }

  updated() {
    console.log("Child --> updated");
  }

  beforeDestroy() {
    console.log("Child --> beforeDestroy");
  }

  destroyed() {
    console.log("Child --> destroyed");
  }
}
</script>

<style lang="less">
.child-layout {
  margin-top: 20px;
  background: #0af;
  width: 100%;
  height: 300px;
  padding: 20px;
  box-sizing: border-box;
}
</style>

放置Parent组件的容器组件的代码:

<template>
  <div>
    <el-button @click="parentToggole">Parent组件显隐</el-button>
    <parent v-if="show" />
  </div>
</template>

<script lang="ts">
import { Component, Vue } from "vue-property-decorator";
import Parent from "./Parent.vue";

@Component({
  name: "Main",
  components: {
    Parent
  }
})
export default class App extends Vue {
  show = true;

  private parentToggole() {
    this.show = !this.show;
  }
}
</script>

开始渲染阶段,父子组件生命周期的调用顺序

  • Parent --> beforeCreate
  • Parent --> created
  • Parent --> beforeMount
  • Child --> beforeCreate
  • Child --> created
  • Child --> beforeMount
  • Child --> mounted
  • Parent --> mounted

父组件某个data属性发生变化,且该属性不会影响子组件

这时只有父组件的beforeUpdate和updated被调用。

测试一下:

在“parent name”输入框中输入值,可以看到控制台只打印了以下2行:

  • Parent --> beforeUpdate
  • Parent --> updated

子组件某个data属性发生变化,且该属性不会影响父组件

只有子组件生命周期的调用beforeUpdate和updated被调用。

测试一下:

在“child name”输入框中输入值,可以看到控制台只打印了以下2行:

  • Child --> beforeUpdate
  • Child --> updated

父组件某个data属性发生变化,且该属性会传递给子组件作为子组件的一个prop,会引起子组件的视图发生变化

这种情况下父组件和子组件的更新生命周期函数都会被调用,且执行的顺序先是父组件准备更新函数beforeUpdate被调用,接着是子组件更新函数调用完之后,最后执行父组件的更新生命周期函数updated:

  • Parent --> beforeUpdate
  • Child --> beforeUpdate
  • Child --> updated
  • Parent --> updated

测试一下:

点击按钮【增加数量】:引起子组件数量的变化,查看控制台打印的情况:

父组件销毁时,会先将子组件销毁

这种情况是先销毁挂载在父组件的子组件,再销毁父组件。所以生命周期函数的调用顺序是:先调用父组件准备销毁的函数beforeDestroy,接着调用子组件的销毁函数,最后调用父组件的销毁函数destroyed:

  • Parent --> beforeDestroy
  • Child --> beforeDestroy
  • Child --> destroyed
  • Parent --> destroyed

测试一下:

点击【Paren组件显隐】按钮,销毁父组件,查看控制台打印情况:

子组件的展示有父组件变量show通过v-if="childShow"来控制时

此时会销毁或者挂载父组件的子组件,更新父组件

使用childShow = false来隐藏子组件时,销毁子组件,更新父组件:

  • Parent --> beforeUpdate
  • Child --> beforeDestroy
  • Child --> destroyed
  • Parent --> updated

测试一下:

点击【Child组件显隐(v-if)】按钮隐藏子组件,查看控制台打印情况:

令childShow = true 时,会重新挂载子组件,那么子组件会是一个完整的挂载过程,而对于父组件而言则是一个更新的过程:

  • Parent --> beforeUpdate
  • Child --> beforeCreate
  • Child --> created
  • Child --> beforeMount
  • Child --> mounted
  • Parent --> updated

测试一下:

再一次点击【Child组件显隐(v-if)】按钮显示子组件,查看控制台打印情况:

子组件被v-show指令控制显隐

先测试一下:

多次点击【Child组件显隐(v-show)】,体验子组件在可见与不可见进行切换的场景,查看一下控制台打印的情况:

由上图控制台的信息可以看见,只有父组件的更新相关的生命周期函数被执行了,没有涉及到子组件的生命周期,这是为什么呢?

先来了解一下v-show的原理:在v-show=false的时候,vue只是给注册v-show指令的dom增加一个style属性display= none,在v-show=true的时候, 便是将style属性display=none删掉。

所以v-show指令不过是通过样式来控制子组件的展示和隐常,并未销毁或者重新挂载一个组件。我们在点击【Child组件显隐(v-show)】隐藏子组件的时候,可以通过浏览器【查看】功能来查看文档结构,可以看见子组件dom还存在:

由于v-if是对一个组件进行销毁或者重新挂载的过程,所以上面通过v-if控制子组件显隐的时候,才会涉及到子组件挂载和销毁的过程。