Skip to content

生命周期

TIP

本小节对应的项目源代码,可以去 刘龙彬/vue3-component-study-2 仓库中下载。

Vue 项目在运行的时候,每个 SFC 组件都会经历一个创建 -> 挂载 -> 更新 -> 销毁的过程。这个过程称为组件的生命周期。一个 SFC 组件完整的生命周期过程如下图所示(共分为 4 个阶段)。

在每个生命周期阶段中,Vue 会自动按顺序执行一系列内置的函数,这些伴随着生命周期执行的函数叫做“生命周期函数(钩子)”:

组件创建阶段

组件创建阶段的主要任务是:初始化 setup 函数初始化选项式 API

image-20240724220124003

setup

在组件的创建阶段,会先执行 setup() 函数。setup 函数是组合式 API 的入口:

vue
<script>
export default {
  // 组合式 API 的入口
  setup() {},
  // 选项式 API
  data() {},
  methods: {}
}
</script>

<template></template>
<style></style>

<script setup>setup() 函数的简写形式:

vue
<script setup>
// 此处,仅支持“组合式 API”的调用,不支持“选项式 API”
</script>

<template></template>
<style></style>

TIP

setup 不是生命周期函数,它只是 Vue3 提供的组合式 API 的入口。

beforeCreate

beforeCreate 是组件创建阶段的第 1 个生命周期函数。在 beforeCreate 生命周期函数被执行的时候,SFC 组件在内存中还没有创建完成,此时选项式 API 也没有被初始化,因此在此生命周期函数中无法访问选项式 API:

vue
<script>
export default {
  setup() {
    console.log('--setup--')
  },
  // 创建阶段
  beforeCreate() {
    console.log('--beforeCreate--')
    // 在 beforeCreate 中无法访问选项式 API 中定义的数据、方法、计算属性等,因为他们还没有被初始化
    console.log(this.num) // 输出:undefined
    this.showNumber() // 报错:Uncaught TypeError: this.showNumber is not a function
  },
  // 选项式 API 的数据
  data() {
    return {
      num: 0
    }
  },
  // 选项式 API 的方法
  methods: {
    showNumber() {
      console.log('data 中 num 的值是:' + this.num)
    }
  }
}
</script>

<template>
  <div>
    <h1>SFC 组件的生命周期</h1>
  </div>
</template>

created

created 是组件创建阶段的第 2 个生命周期函数。在 created 生命周期函数执行的时候,Vue 的组件渲染器已经完成了对当前组件的选项式 API 的初始化。因此,在 created 中可以访问到组件上声明的选项式 API 的数据、方法、计算属性等:

vue
<script>
export default {
  setup() {
    console.log('--setup--')
  },
  // 创建阶段
  beforeCreate() {
    console.log('--beforeCreate--')
  },
  created() {
    console.log('--created--')
    // 输出:0
    console.log(this.num)
    // 输出:
    // data 中 num 的值是:0
    // 计算属性 numPlus2 的值是:0
    this.showNumber()
  },
  // 选项式 API 的数据
  data() {
    return {
      num: 0
    }
  },
  // 选项式 API 的方法
  methods: {
    showNumber() {
      console.log('data 中 num 的值是:' + this.num)
      console.log('计算属性 numPlus2 的值是:' + this.numPlus2)
    }
  },
  // 选项式 API 的计算属性
  computed: {
    numPlus2() {
      return this.num * 2
    }
  }
}
</script>

<template>
  <div>
    <h1>SFC 组件的生命周期</h1>
  </div>
</template>

TIP

当 created 函数执行完毕之后,组件创建阶段的生命周期函数就执行完毕了,组件会自动进入到挂载阶段并执行挂载阶段的生命周期函数。

组件挂载阶段

组件挂载阶段的主要任务是:编译 <template> 中的模板内容并把组件渲染到浏览器中。

image-20240724223050992

预编译模板 & 即时编译模板

TIP

预编译模板:通过构建工具对 SFC 组件进行预构建之后,得到的就是预编译的模板。在浏览器中仅需要 Runtime 即可渲染组件结构。

即时编译模板:没有通过构建工具对 SFC 进行预构建,而是在浏览器中先调用 Compiler 进行即时编译,然后再通过 Runtime 渲染组件模板。

在刚进入组件的挂载阶段时,Vue 会先判断当前组件中是否存在预编译的模板,如果存在,则直接把预编译模板交给 Runtime 进行渲染。否则会先调用 Compiler 编译模板,在调用 Runtime 渲染。

beforeMount

beforeMount 是组件挂载阶段的第 1 个生命周期函数。此时仅仅把组件的模板编译成了内存中的虚拟 DOM,但是还没有把虚拟 DOM 渲染为浏览器中真实的 DOM 节点。因此在 beforeMount 中无法获取组件中的 DOM 元素:

vue
<script>
export default {
  setup() {
    console.log('--setup--')
  },
  // 创建阶段
  beforeCreate() {
    console.log('--beforeCreate--')
  },
  created() {
    console.log('--created--')
  },
  // 运行阶段
  beforeMount() {
    console.log('--beforeMount--')
    // 在 beforeMount 中无法获取当前组件的真实 DOM,因为此时尚未渲染真实 DOM
    const titleDOM = document.querySelector('.title')
    console.log(titleDOM) // 输出:null
  },
  // 选项式 API 的数据
  data() {
    return {
      num: 0
    }
  },
  // 选项式 API 的方法
  methods: {
    showNumber() {
      console.log('data 中 num 的值是:' + this.num)
      console.log('计算属性 numPlus2 的值是:' + this.numPlus2)
    }
  },
  // 选项式 API 的计算属性
  computed: {
    numPlus2() {
      return this.num * 2
    }
  }
}
</script>

<template>
  <div>
    <h1 class="title">SFC 组件的生命周期</h1>
  </div>
</template>

在组合式 API 中可以使用 onBeforeMount(fn) API 来替代选项式 API 中的 beforeMount 函数:

vue
<script>
// 导入选项式 API
import { onBeforeMount } from 'vue'

export default {
  setup() {
    console.log('--setup--')
    // 在 beforeMount 阶段执行 fn 函数
    onBeforeMount(() => {
      console.log('--组合式 API:onBeforeMount--')
      // 在 beforeMount 中无法获取当前组件的真实 DOM,因为此时尚未渲染真实 DOM
      const titleDOM = document.querySelector('.title')
      console.log(titleDOM) // 输出:null
    })
  },
  beforeMount() {
    console.log('--beforeMount--')
    // 在 beforeMount 中无法获取当前组件的真实 DOM,因为此时尚未渲染真实 DOM
    const titleDOM = document.querySelector('.title')
    console.log(titleDOM) // 输出:null
  },
  // 选项式 API 的数据
  data() {
    return {
      num: 0
    }
  },
  // 选项式 API 的方法
  methods: {
    showNumber() {
      console.log('data 中 num 的值是:' + this.num)
      console.log('计算属性 numPlus2 的值是:' + this.numPlus2)
    }
  },
  // 选项式 API 的计算属性
  computed: {
    numPlus2() {
      return this.num * 2
    }
  }
}
</script>

<template>
  <div>
    <h1 class="title">SFC 组件的生命周期</h1>
  </div>
</template>

mounted

mounted 是组件挂载阶段的第 2 个生命周期函数。此时组件的模板已经完成了首次挂载,浏览器的页面中已存在当前组件的真实 DOM。很多依赖于 DOM 元素的第三方插件的初始化工作,都需要在 mounted 函数中执行(例如:把 div 初始化为 echarts 的图表容器):

vue
<script>
import { onBeforeMount } from 'vue'
import * as echarts from 'echarts'

export default {
  setup() {
    console.log('--setup--')
    onBeforeMount(() => console.log('--组合式 API:onBeforeMount--'))
  },
  // 创建阶段
  beforeCreate() {
    console.log('--beforeCreate--')
  },
  created() {
    console.log('--created--')
  },
  // 挂载阶段
  beforeMount() {
    console.log('--beforeMount--')
  },
  mounted() {
    console.log('--mounted--')
    // 可以获取到真实的 DOM 元素
    const dom = document.querySelector('#main')
    // 基于准备好的dom,初始化echarts实例
    const myChart = echarts.init(dom)
    myChart.setOption({
      title: {
        text: 'ECharts 入门示例'
      },
      tooltip: {},
      xAxis: {
        data: ['衬衫', '羊毛衫', '雪纺衫', '裤子', '高跟鞋', '袜子']
      },
      yAxis: {},
      series: [
        {
          name: '销量',
          type: 'bar',
          data: [5, 20, 36, 10, 10, 20]
        }
      ]
    })
  },
  // 选项式 API 的数据
  data() {
    return {
      num: 0
    }
  },
  // 选项式 API 的方法
  methods: {
    showNumber() {
      console.log('data 中 num 的值是:' + this.num)
      console.log('计算属性 numPlus2 的值是:' + this.numPlus2)
    }
  },
  // 选项式 API 的计算属性
  computed: {
    numPlus2() {
      return this.num * 2
    }
  }
}
</script>

<template>
  <div>
    <h1 class="title">SFC 组件的生命周期</h1>
    <!-- 图表容器 -->
    <div id="main" style="width: 600px; height: 400px"></div>
  </div>
</template>

与之对应的,在组合式 API 中可以使用 onMounted API 来替代选项式 API 中的 mounted 函数:

vue
<script>
import { onBeforeMount, onMounted } from 'vue'
import * as echarts from 'echarts'

export default {
  setup() {
    console.log('--setup--')
    onBeforeMount(() => console.log('--组合式 API:onBeforeMount--'))
    onMounted(() => {
      console.log('--组合式 API:onMounted--')
      // 可以获取到真实的 DOM 元素
      const dom = document.querySelector('#main')
      // 基于准备好的dom,初始化echarts实例
      const myChart = echarts.init(dom)
      myChart.setOption({
        title: {
          text: 'ECharts 入门示例'
        },
        tooltip: {},
        xAxis: {
          data: ['衬衫', '羊毛衫', '雪纺衫', '裤子', '高跟鞋', '袜子']
        },
        yAxis: {},
        series: [
          {
            name: '销量',
            type: 'bar',
            data: [5, 20, 36, 10, 10, 20]
          }
        ]
      })
    })
  },
  // 创建阶段
  beforeCreate() {
    console.log('--beforeCreate--')
  },
  created() {
    console.log('--created--')
  },
  // 挂载阶段
  beforeMount() {
    console.log('--beforeMount--')
  },
  mounted() {
    console.log('--mounted--')
  },
  // 选项式 API 的数据
  data() {
    return {
      num: 0
    }
  },
  // 选项式 API 的方法
  methods: {
    showNumber() {
      console.log('data 中 num 的值是:' + this.num)
      console.log('计算属性 numPlus2 的值是:' + this.numPlus2)
    }
  },
  // 选项式 API 的计算属性
  computed: {
    numPlus2() {
      return this.num * 2
    }
  }
}
</script>

<template>
  <div>
    <h1 class="title">SFC 组件的生命周期</h1>
    <div id="main" style="width: 600px; height: 400px"></div>
  </div>
</template>

组件更新阶段

组件更新阶段的主要任务是:根据数据的变化,更新并重新渲染组件的 DOM 结构。

image-20240724235626717

beforeUpdate

当组件中的响应式数据(data、computed、props)发生变化之后,会先执行更新阶段的 beforeUpdate 生命周期函数。

此时组件中的数据是新的,但组件的 DOM 结构尚未被重新渲染,因此无法获取到更新后最新的 DOM 元素。

vue
<script>
import { onBeforeMount, onMounted } from 'vue'
import * as echarts from 'echarts'

export default {
  setup() {
    console.log('--setup--')
    onBeforeMount(() => console.log('--组合式 API:onBeforeMount--'))
    onMounted(() => console.log('--组合式 API:onMounted--'))
  },
  // 创建阶段
  beforeCreate() {
    console.log('--beforeCreate--')
  },
  created() {
    console.log('--created--')
  },
  // 挂载阶段
  beforeMount() {
    console.log('--beforeMount--')
  },
  mounted() {
    console.log('--mounted--')
  },
  // 更新阶段
  beforeUpdate() {
    console.log('--beforeUpdate--')
    const dom = document.querySelector('.num')
    console.log(`当前的数据是:${this.num},DOM 中的文本内容是:${dom.textContent}`)
  },
  // 选项式 API 的数据
  data() {
    return {
      num: 0
    }
  },
  // 选项式 API 的方法
  methods: {
    showNumber() {
      console.log('data 中 num 的值是:' + this.num)
      console.log('计算属性 numPlus2 的值是:' + this.numPlus2)
    }
  },
  // 选项式 API 的计算属性
  computed: {
    numPlus2() {
      return this.num * 2
    }
  }
}
</script>

<template>
  <div>
    <h1 class="title">SFC 组件的生命周期</h1>
    <p class="num" @click="num++">num 的值是:{{ num }}</p>
    <div id="main" style="width: 600px; height: 400px"></div>
  </div>
</template>

与之对应的,在组合式 API 中可以使用 onBeforeUpdate API 来替代选项式 API 中的 beforeUpdate 函数:

vue
<script>
import { ref, onBeforeMount, onMounted, onBeforeUpdate } from 'vue'

export default {
  setup() {
    console.log('--setup--')
    const count = ref(0)

    onBeforeMount(() => console.log('--组合式 API:onBeforeMount--'))
    onMounted(() => console.log('--组合式 API:onMounted--'))
    onBeforeUpdate(() => {
      console.log('--组合式 API:onBeforeUpdate--')
      const dom = document.querySelector('.count')
      console.log(`当前的数据是:${count.value},DOM 中的文本内容是:${dom.textContent}`)
    })

    return {
      count
    }
  },
  // 创建阶段
  beforeCreate() {
    console.log('--beforeCreate--')
  },
  created() {
    console.log('--created--')
  },
  // 挂载阶段
  beforeMount() {
    console.log('--beforeMount--')
  },
  mounted() {
    console.log('--mounted--')
  },
  // 更新阶段
  beforeUpdate() {
    console.log('--beforeUpdate--')
  },
  // 选项式 API 的数据
  data() {
    return {
      num: 0
    }
  },
  // 选项式 API 的方法
  methods: {
    showNumber() {
      console.log('data 中 num 的值是:' + this.num)
      console.log('计算属性 numPlus2 的值是:' + this.numPlus2)
    }
  },
  // 选项式 API 的计算属性
  computed: {
    numPlus2() {
      return this.num * 2
    }
  }
}
</script>

<template>
  <div>
    <h1 class="title">SFC 组件的生命周期</h1>
    <p class="num" @click="num++">num 的值是:{{ num }}</p>
    <p class="count" @click="count++">count 的值是:{{ count }}</p>
    <div id="main" style="width: 600px; height: 400px"></div>
  </div>
</template>

updated

updated 是更新阶段的第 2 个生命周期函数。此时组件已经完成了 DOM 的更新渲染。因此在 updated 中获取到的数据和 DOM 都是最新的。

vue
<script>
import { ref, onBeforeMount, onMounted, onBeforeUpdate } from 'vue'

export default {
  setup() {
    console.log('--setup--')
    const count = ref(0)

    onBeforeMount(() => console.log('--组合式 API:onBeforeMount--'))
    onMounted(() => console.log('--组合式 API:onMounted--'))
    onBeforeUpdate(() => console.log('--组合式 API:onBeforeUpdate--'))

    return {
      count
    }
  },
  // 创建阶段
  beforeCreate() {
    console.log('--beforeCreate--')
  },
  created() {
    console.log('--created--')
  },
  // 挂载阶段
  beforeMount() {
    console.log('--beforeMount--')
  },
  mounted() {
    console.log('--mounted--')
  },
  // 更新阶段
  beforeUpdate() {
    console.log('--beforeUpdate--')
  },
  updated() {
    console.log('--updated--')
    const dom = document.querySelector('.num')
    console.log(`当前的数据是:${this.num},DOM 中的文本内容是:${dom.textContent}`)
  },
  // 选项式 API 的数据
  data() {
    return {
      num: 0
    }
  },
  // 选项式 API 的方法
  methods: {
    showNumber() {
      console.log('data 中 num 的值是:' + this.num)
      console.log('计算属性 numPlus2 的值是:' + this.numPlus2)
    }
  },
  // 选项式 API 的计算属性
  computed: {
    numPlus2() {
      return this.num * 2
    }
  }
}
</script>

<template>
  <div>
    <h1 class="title">SFC 组件的生命周期</h1>
    <p class="num" @click="num++">num 的值是:{{ num }}</p>
    <p class="count" @click="count++">count 的值是:{{ count }}</p>
    <div id="main" style="width: 600px; height: 400px"></div>
  </div>
</template>

与之对应的,在组合式 API 中可以使用 onUpdated API 来替代选项式 API 中的 updated 函数:

vue
<script>
import { ref, onBeforeMount, onMounted, onBeforeUpdate, onUpdated } from 'vue'

export default {
  setup() {
    console.log('--setup--')
    const count = ref(0)

    onBeforeMount(() => console.log('--组合式 API:onBeforeMount--'))
    onMounted(() => console.log('--组合式 API:onMounted--'))
    onBeforeUpdate(() => console.log('--组合式 API:onBeforeUpdate--'))
    onUpdated(() => {
      console.log('--组合式 API:onUpdated--')
      const dom = document.querySelector('.count')
      console.log(`当前的数据是:${count.value},DOM 中的文本内容是:${dom.textContent}`)
    })

    return {
      count
    }
  },
  // 创建阶段
  beforeCreate() {
    console.log('--beforeCreate--')
  },
  created() {
    console.log('--created--')
  },
  // 挂载阶段
  beforeMount() {
    console.log('--beforeMount--')
  },
  mounted() {
    console.log('--mounted--')
  },
  // 更新阶段
  beforeUpdate() {
    console.log('--beforeUpdate--')
  },
  updated() {
    console.log('--updated--')
  },
  // 选项式 API 的数据
  data() {
    return {
      num: 0
    }
  },
  // 选项式 API 的方法
  methods: {
    showNumber() {
      console.log('data 中 num 的值是:' + this.num)
      console.log('计算属性 numPlus2 的值是:' + this.numPlus2)
    }
  },
  // 选项式 API 的计算属性
  computed: {
    numPlus2() {
      return this.num * 2
    }
  }
}
</script>

<template>
  <div>
    <h1 class="title">SFC 组件的生命周期</h1>
    <p class="num" @click="num++">num 的值是:{{ num }}</p>
    <p class="count" @click="count++">count 的值是:{{ count }}</p>
    <div id="main" style="width: 600px; height: 400px"></div>
  </div>
</template>

触发次数

组件更新阶段的生命周期函数,至少执行 0 次,最多执行 N 次:

  • 如果组件挂载之后,数据从未发生变化,则更新阶段的生命周期函数执行 0 次;
  • 如果组件的数据变化了 N 次,则更新阶段的生命周期函数会执行 N 次;

组件销毁阶段

组件销毁阶段的主要任务是:在组件销毁时手动清理一些副作用,例如:计时器、DOM 事件监听器或者与服务器的连接。

image-20240725110631550

beforeUnmount 和 unmounted

beforeUnmount 是销毁阶段的第 1 个生命周期函数,它在组件实例被卸载之前调用,此时组件实例依然还保有全部的功能。

unmounted 是销毁阶段的第 2 个生命周期函数,它在组件实例被卸载之后调用,此时:

  • 其所有子组件都已经被卸载;
  • 所有相关的响应式作用 (渲染作用以及 setup() 时创建的计算属性和侦听器) 都已经停止,DOM 元素不可用;
vue
<script>
import { ref, onBeforeMount, onMounted, onBeforeUpdate, onUpdated } from 'vue'

export default {
  setup() {
    console.log('--setup--')
    const count = ref(0)

    onBeforeMount(() => console.log('--组合式 API:onBeforeMount--'))
    onMounted(() => console.log('--组合式 API:onMounted--'))
    onBeforeUpdate(() => console.log('--组合式 API:onBeforeUpdate--'))
    onUpdated(() => console.log('--组合式 API:onUpdated--'))

    return {
      count
    }
  },
  // 创建阶段
  beforeCreate() {
    console.log('--beforeCreate--')
  },
  created() {
    console.log('--created--')
  },
  // 挂载阶段
  beforeMount() {
    console.log('--beforeMount--')
  },
  mounted() {
    console.log('--mounted--')
  },
  // 更新阶段
  beforeUpdate() {
    console.log('--beforeUpdate--')
  },
  updated() {
    console.log('--updated--')
  },
  // 销毁阶段
  beforeUnmount() {
    console.log('--beforeUnmount--')
    const dom = document.querySelector('.count')
    console.log(`当前的数据是:${this.count},DOM 中的文本内容是:${dom.textContent}`)
  },
  unmounted() {
    console.log('--unmounted--')
    // 下面的代码会报错,因为 DOM 已经卸载
    // const dom = document.querySelector('.count')
    // console.log(`当前的数据是:${this.count},DOM 中的文本内容是:${dom.textContent}`)
  },
  // 选项式 API 的数据
  data() {
    return {
      num: 0
    }
  },
  // 选项式 API 的方法
  methods: {
    showNumber() {
      console.log('data 中 num 的值是:' + this.num)
      console.log('计算属性 numPlus2 的值是:' + this.numPlus2)
    }
  },
  // 选项式 API 的计算属性
  computed: {
    numPlus2() {
      return this.num * 2
    }
  }
}
</script>

<template>
  <div>
    <h1 class="title">SFC 组件的生命周期</h1>
    <p class="num" @click="num++">num 的值是:{{ num }}</p>
    <p class="count" @click="count++">count 的值是:{{ count }}</p>
    <div id="main" style="width: 600px; height: 400px"></div>
  </div>
</template>

与之对应的,在组合式 API 中可以使用 onBeforeUnmountonUnmounted API 来替代选项式 API 中的 beforeUnmountunmounted 函数:

vue
<script>
import { ref, computed, onBeforeMount, onMounted, onBeforeUpdate, onUpdated, onBeforeUnmount, onUnmounted } from 'vue'

export default {
  setup() {
    console.log('--setup--')
    const count = ref(0)
    const countPlus2 = computed(() => count.value * 2)

    const showCount = () => {
      console.log('count 的值是:' + count.value)
      console.log('计算属性 countPlus2 的值是:' + countPlus2.value)
    }

    onBeforeMount(() => console.log('--组合式 API:onBeforeMount--'))
    onMounted(() => console.log('--组合式 API:onMounted--'))
    onBeforeUpdate(() => console.log('--组合式 API:onBeforeUpdate--'))
    onUpdated(() => console.log('--组合式 API:onUpdated--'))
    // 组件销毁阶段的生命周期函数
    onBeforeUnmount(() => {
      console.log('--组合式 API:onBeforeUnmount--')
      const dom = document.querySelector('.count')
      console.log(`当前的数据是:${count.value},DOM 中的文本内容是:${dom.textContent}`)
    })
    onUnmounted(() => {
      console.log('--组合式 API:onUnmounted--')
      // 下面的代码会报错,因为 DOM 已经卸载
      // const dom = document.querySelector('.count')
      // console.log(`当前的数据是:${count.value},DOM 中的文本内容是:${dom.textContent}`)
    })

    return {
      count
    }
  },
  // 创建阶段
  beforeCreate() {
    console.log('--beforeCreate--')
  },
  created() {
    console.log('--created--')
  },
  // 挂载阶段
  beforeMount() {
    console.log('--beforeMount--')
  },
  mounted() {
    console.log('--mounted--')
  },
  // 更新阶段
  beforeUpdate() {
    console.log('--beforeUpdate--')
  },
  updated() {
    console.log('--updated--')
  },
  // 销毁阶段
  beforeUnmount() {
    console.log('--beforeUnmount--')
  },
  unmounted() {
    console.log('--unmounted--')
  },
  // 选项式 API 的数据
  data() {
    return {
      num: 0
    }
  },
  // 选项式 API 的方法
  methods: {
    showNumber() {
      console.log('data 中 num 的值是:' + this.num)
      console.log('计算属性 numPlus2 的值是:' + this.numPlus2)
    }
  },
  // 选项式 API 的计算属性
  computed: {
    numPlus2() {
      return this.num * 2
    }
  }
}
</script>

<template>
  <div>
    <h1 class="title">SFC 组件的生命周期</h1>
    <p class="num" @click="num++">num 的值是:{{ num }}</p>
    <p class="count" @click="count++">count 的值是:{{ count }}</p>
    <div id="main" style="width: 600px; height: 400px"></div>
  </div>
</template>

onBeforeUnmount 的用法示例

在 onMounted 中开启的定时器,需要在 onBeforeUnmount 中手动清除。否则当组件被卸载后,定时器也会继续执行:

vue
<script setup>
import { ref, onMounted, onBeforeUnmount } from 'vue'
const count = ref(0)
let timerId = null

// 组件挂载完成后,开启定时器
onMounted(() => {
  timerId = setInterval(() => {
    count.value++
    console.log('触发了定时器')
  }, 1000)
})

// 当组件即将卸载,手动清理定时器
onBeforeUnmount(() => {
  clearInterval(timerId)
  timerId = null
})
</script>

<template>
  <h1>count 的值是:{{ count }}</h1>
</template>

嵌套组件的生命周期函数的执行顺序

假设有 Level1、Level2、Level3 三个组件相互嵌套。其中 Level1 是父组件,Level2 是子组件,Level3 是孙组件。

组件创建阶段,这三个组件的生命周期函数的执行顺序分别是:

# 根据嵌套顺序,先依次执行父组件的 onBeforeMount
level-1 onBeforeMount
level-2 onBeforeMount
level-3 onBeforeMount

# 再从内向外,依次执行子组件的 onMounted
level-3 onMounted
level-2 onMounted
level-1 onMounted

组件销毁阶段,这三个组件的生命周期函数的执行顺序分别是:

# 根据嵌套顺序,先依次执行父组件的 onBeforeUnmount
level-1 onBeforeUnmount
level-2 onBeforeUnmount
level-3 onBeforeUnmount

# 再从内向外,依次执行子组件的 onUnmounted
level-3 onUnmounted
level-2 onUnmounted
level-1 onUnmounted

在组件更新阶段,这三个组件互不影响,只会触发自己的 onBeforeUpdateonUpdated

示例代码如下:

vue
<!-- App.vue -->
<script setup>
import { ref } from 'vue'
import Level1 from './components/Level1.vue'

const flag = ref(true)
</script>

<template>
  <button @click="flag = !flag">Toggle</button>
  <hr />
  <Level1 v-if="flag" />
</template>
vue
<!-- Level1.vue -->
<script setup>
import Level2 from './Level2.vue'
import { onBeforeMount, onMounted, onBeforeUpdate, onUpdated, onBeforeUnmount, onUnmounted, ref } from 'vue'

onBeforeMount(() => console.log('level-1 onBeforeMount'))
onMounted(() => console.log('level-1 onMounted'))

onBeforeUpdate(() => console.log('level-1 onBeforeUpdate'))
onUpdated(() => console.log('level-1 onUpdated'))

onBeforeUnmount(() => console.log('level-1 onBeforeUnmount'))
onUnmounted(() => console.log('level-1 onUnmounted'))

const c1 = ref(0)
</script>

<template>
  <h1>Level-1 组件</h1>
  <p @click="c1++">c1 的值:{{ c1 }}</p>
  <hr />
  <Level2 />
</template>
vue
<!-- Level2.vue -->
<script setup>
import Level3 from './Level3.vue'
import { onBeforeMount, onMounted, onBeforeUpdate, onUpdated, onBeforeUnmount, onUnmounted, ref } from 'vue'

onBeforeMount(() => console.log('level-2 onBeforeMount'))
onMounted(() => console.log('level-2 onMounted'))

onBeforeUpdate(() => console.log('level-2 onBeforeUpdate'))
onUpdated(() => console.log('level-2 onUpdated'))

onBeforeUnmount(() => console.log('level-2 onBeforeUnmount'))
onUnmounted(() => console.log('level-2 onUnmounted'))

const c2 = ref(0)
</script>

<template>
  <h2>Level-2 组件</h2>
  <p @click="c2++">c2 的值:{{ c2 }}</p>
  <hr />
  <Level3 />
</template>
vue
<!-- Level3.vue -->
<script setup>
import { onBeforeMount, onMounted, onBeforeUpdate, onUpdated, onBeforeUnmount, onUnmounted, ref } from 'vue'

onBeforeMount(() => console.log('level-3 onBeforeMount'))
onMounted(() => console.log('level-3 onMounted'))

onBeforeUpdate(() => console.log('level-3 onBeforeUpdate'))
onUpdated(() => console.log('level-3 onUpdated'))

onBeforeUnmount(() => console.log('level-3 onBeforeUnmount'))
onUnmounted(() => console.log('level-3 onUnmounted'))

const c3 = ref(0)
</script>

<template>
  <h3>Level-3 组件</h3>
  <p @click="c3++">c3 的值:{{ c3 }}</p>
</template>

总结

  1. 除了 beforeCreatecreated 之外,其它的选项式生命周期函数,都存在与之对应的组合式生命周期函数,即 onXXX
  2. 若同时定义了选项式和组合式的生命周期函数,则组合式优先于选项式执行。
  3. 组件更新阶段的生命周期函数,会根据数据的变化执行 0 次或多次,而创建、挂载、销毁阶段的生命周期函数仅执行 1 次。
  4. 多个组件相互嵌套时,会根据组件嵌套的顺序,由外向内依次执行 onBeforeXXX 生命周期函数,再由内向外依次执行 onXXX 生命周期函数(仅适用于“挂载”和“销毁”阶段的生命周期函数)。

天不生夫子,万古长如夜