Vue3 基本生命周期:组件的一生之旅

本文是 Vue3 系列第八篇,将深入探讨 Vue3 组件的生命周期。理解生命周期就像是了解一个人的成长历程,从出生到成长,再到衰老和离去,每个阶段都有其特定的意义和任务。掌握组件的生命周期,能够让我们在合适的时机执行合适的操作,构建出更加健壮和高效的 Vue 应用。

一、生命周期的深刻理解

人的生命周期类比

要理解组件的生命周期,我们可以用一个生动的比喻:把组件想象成一个人,它也有自己的"人生阶段"。

出生阶段(创建)

就像婴儿刚来到这个世界,组件也开始它的生命。在这个阶段,组件刚刚被创建,但还没有真正出现在界面上。就像婴儿有了生命体征,但还没有开始与外界互动。

成长阶段(挂载)

这个阶段相当于人开始接触世界、学习知识。组件被添加到 DOM 中,开始与用户交互,显示内容,响应操作。就像孩子开始上学,认识朋友,参与社会活动。

变化阶段(更新)

人生中会经历很多变化,比如换工作、搬家、学习新技能。同样,当组件的数据发生变化时,它需要更新自己来反映这些变化。这个阶段组件会重新渲染,展示最新的状态。

离别阶段(卸载)

就像生命终有尽头,组件也会在不再需要时被销毁。在这个阶段,组件会清理自己占用的资源,移除事件监听器,准备从内存中消失。

组件的四个核心生命周期阶段

每个 Vue 组件都会经历四个主要的生命周期阶段:

  1. 创建阶段(Creation):组件实例被创建,但还没有挂载到 DOM

  2. 挂载阶段(Mounting):组件被挂载到 DOM,开始与用户交互

  3. 更新阶段(Updating):响应数据变化,重新渲染组件

  4. 卸载阶段(Unmounting):组件被销毁,清理资源

特定时刻调用特定函数的意义

生命周期的核心价值在于:在特定的时刻执行特定的代码。这就像我们在人生的重要节点做出重要决定一样:

  • 出生时:准备必要的生存条件

  • 上学时:获取知识和技能

  • 工作时:发挥能力创造价值

  • 退休时:整理一生,安享晚年

在组件开发中,我们利用生命周期函数来:

  • 在组件创建时初始化数据

  • 在挂载完成后操作 DOM

  • 在更新前后执行特定逻辑

  • 在销毁前清理资源,避免内存泄漏

二、Vue2 的生命周期详解

创建阶段:beforeCreate() 和 created()

让我们通过一个简单的计数器组件来理解 Vue2 的生命周期:

html 复制代码
<template>
  <div>
    <p>计数: {{ count }}</p>
    <button @click="increment">增加</button>
  </div>
</template>

<script>
export default {
  data() {
    return {
      count: 0
    }
  },
  methods: {
    increment() {
      this.count++
    }
  }
}
</script>

现在我们在其中添加创建阶段的钩子函数:

html 复制代码
<template>
  <div>
    <p>计数: {{ count }}</p>
    <button @click="increment">增加</button>
  </div>
</template>

<script>
export default {
  data() {
    return {
      count: 0
    }
  },
  // 创建阶段开始 - 组件实例刚被创建
  beforeCreate() {
    console.log('beforeCreate - 组件实例刚创建')
    console.log('数据 count:', this.count) // undefined
    console.log('方法 increment:', this.increment) // undefined
  },
  // 创建阶段完成 - 数据观测和事件配置已完成
  created() {
    console.log('created - 数据观测已完成')
    console.log('数据 count:', this.count) // 0
    console.log('方法 increment:', this.increment) // 函数
  },
  methods: {
    increment() {
      this.count++
    }
  }
}
</script>

创建阶段的详细解说:

beforeCreate()

  • 时机:在组件实例刚被创建时调用

  • 状态:数据观测(data)和事件配置(methods)都尚未初始化

  • 用途:通常用于插件开发,普通业务中很少使用

created()

  • 时机:在组件实例创建完成后调用

  • 状态:数据观测已完成,方法和计算属性可用,但 DOM 还未生成

  • 用途:常用于数据初始化、API 调用等不需要操作 DOM 的任务

这个阶段就像是婴儿刚刚出生,有了基本的生命体征(数据和方法),但还没有开始与外界互动(DOM 操作)。

挂载阶段:beforeMount() 和 mounted()

现在让我们看看挂载阶段的钩子函数:

html 复制代码
<template>
  <div>
    <p>计数: {{ count }}</p>
    <button @click="increment">增加</button>
  </div>
</template>

<script>
export default {
  data() {
    return {
      count: 0
    }
  },
  // 挂载阶段开始 - 模板编译完成,即将挂载到 DOM
  beforeMount() {
    console.log('beforeMount - 模板编译完成,即将挂载')
    console.log('DOM 元素:', this.$el) // undefined,因为还没挂载
  },
  // 挂载阶段完成 - 组件已挂载到 DOM
  mounted() {
    console.log('mounted - 组件已挂载到 DOM')
    console.log('DOM 元素:', this.$el) // 实际的 DOM 元素
    console.log('计数元素:', this.$el.querySelector('p').textContent)
  },
  methods: {
    increment() {
      this.count++
    }
  }
}
</script>

挂载阶段的详细解说:

beforeMount()

  • 时机:在挂载开始之前调用,模板已经编译完成

  • 状态:虚拟 DOM 已创建,但尚未转换为真实 DOM

  • 用途:在组件首次渲染前执行最后准备

mounted()

  • 时机:在组件挂载到 DOM 后调用

  • 状态:组件已完全初始化,可以访问 DOM 元素

  • 用途:常用于需要操作 DOM 的任务,如初始化第三方库、添加事件监听器等

这个阶段就像是人开始步入社会,具备了与外界交互的能力,可以开始施展才华了。

更新阶段:beforeUpdate() 和 updated()

当组件数据发生变化时,会进入更新阶段:

html 复制代码
<template>
  <div>
    <p>计数: {{ count }}</p>
    <button @click="increment">增加</button>
  </div>
</template>

<script>
export default {
  data() {
    return {
      count: 0
    }
  },
  // 更新阶段开始 - 数据变化,虚拟 DOM 重新渲染之前
  beforeUpdate() {
    console.log('beforeUpdate - 数据已变化,即将重新渲染')
    console.log('当前计数:', this.count)
    console.log('DOM 内容:', this.$el.querySelector('p').textContent)
  },
  // 更新阶段完成 - 虚拟 DOM 已重新渲染并更新到真实 DOM
  updated() {
    console.log('updated - 组件已更新')
    console.log('当前计数:', this.count)
    console.log('DOM 内容:', this.$el.querySelector('p').textContent)
  },
  methods: {
    increment() {
      this.count++
    }
  }
}
</script>

更新阶段的详细解说:

beforeUpdate()

  • 时机:在数据变化后,虚拟 DOM 重新渲染之前调用

  • 状态:数据已更新,但界面还未反映变化

  • 用途:在更新前获取当前状态,或执行更新前的逻辑

updated()

  • 时机:在数据变化导致虚拟 DOM 重新渲染和更新后调用

  • 状态:组件已更新,DOM 已同步到最新状态

  • 用途:执行依赖于更新后 DOM 的操作

这个阶段就像是人在生活中不断学习和成长,适应新的环境和挑战。

销毁阶段:beforeDestroy() 和 destroyed()

当组件不再需要时,会进入销毁阶段:

html 复制代码
<template>
  <div>
    <p>计数: {{ count }}</p>
    <button @click="increment">增加</button>
    <button @click="$destroy()">销毁组件</button>
  </div>
</template>

<script>
export default {
  data() {
    return {
      count: 0,
      timer: null
    }
  },
  mounted() {
    this.timer = setInterval(() => {
      console.log('计时器运行中...')
    }, 1000)
  },
  // 销毁阶段开始 - 组件实例销毁之前
  beforeDestroy() {
    console.log('beforeDestroy - 组件即将销毁')
    // 清理工作,如清除定时器、取消事件监听等
    clearInterval(this.timer)
    console.log('计时器已清理')
  },
  // 销毁阶段完成 - 组件实例已销毁
  destroyed() {
    console.log('destroyed - 组件已销毁')
    console.log('数据 count:', this.count) // 仍可访问,但已无意义
  },
  methods: {
    increment() {
      this.count++
    }
  }
}
</script>

销毁阶段的详细解说:

beforeDestroy()

  • 时机:在组件实例销毁之前调用

  • 状态:组件实例仍然完全可用

  • 用途:执行清理操作,如清除定时器、取消网络请求、移除事件监听等

destroyed()

  • 时机:在组件实例销毁之后调用

  • 状态:组件实例的所有指令都已解绑,事件监听器已移除

  • 用途:执行最终的清理工作

这个阶段就像是人生的晚年,整理一生的经历,安排好身后事,平静地离开。

三、Vue3 的生命周期详解

Vue3 在保留 Vue2 生命周期概念的基础上,进行了一些重要的改进和优化。最大的变化是引入了组合式 API,让生命周期函数的使用更加灵活。

创建阶段的改变

在 Vue3 中,创建阶段的行为有所变化:

html 复制代码
<template>
  <div>
    <p>计数: {{ count }}</p>
    <button @click="increment">增加</button>
  </div>
</template>

<script setup>
import { ref } from 'vue'

const count = ref(0)

const increment = () => {
  count.value++
}

// 在 Vue3 的 <script setup> 中,代码会立即执行
console.log('这相当于在 created 阶段执行')
console.log('数据 count:', count.value)
console.log('方法 increment:', increment)
</script>

Vue3 创建阶段的变化:

  1. 没有 beforeCreate 和 created:在组合式 API 中,这两个钩子被 setup 函数替代

  2. setup 函数执行时机:相当于在 beforeCreate 和 created 之间执行

  3. 更直观的代码组织 :在 <script setup> 中的代码会在组件创建时立即执行

这种改变让代码更加直观,我们不需要再记忆特定的钩子函数名称,只需要在 setup 中编写初始化逻辑即可。

挂载阶段:onBeforeMount() 和 onMounted()

Vue3 的挂载阶段钩子以函数形式提供:

html 复制代码
<template>
  <div>
    <p>计数: {{ count }}</p>
    <button @click="increment">增加</button>
  </div>
</template>

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

const count = ref(0)
const increment = () => {
  count.value++
}

// 挂载前钩子
onBeforeMount(() => {
  console.log('onBeforeMount - 即将挂载到 DOM')
  console.log('DOM 元素还未可用')
})

// 挂载后钩子
onMounted(() => {
  console.log('onMounted - 已挂载到 DOM')
  console.log('现在可以操作 DOM 元素')
  const countElement = document.querySelector('p')
  console.log('计数元素:', countElement.textContent)
})
</script>

Vue3 挂载阶段的特点:

  1. 函数式 API:生命周期钩子以函数形式导入和使用

  2. 更好的 TypeScript 支持:完整的类型推断

  3. 更灵活的代码组织:可以在任何地方使用生命周期钩子

  4. 相同的执行时机:与 Vue2 的对应钩子执行时机相同

更新阶段:onBeforeUpdate() 和 onUpdated()

Vue3 的更新阶段钩子:

html 复制代码
<template>
  <div>
    <p>计数: {{ count }}</p>
    <button @click="increment">增加</button>
  </div>
</template>

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

const count = ref(0)

const increment = () => {
  count.value++
}

// 更新前钩子
onBeforeUpdate(() => {
  console.log('onBeforeUpdate - 数据已变化,即将重新渲染')
  console.log('当前计数:', count.value)
})

// 更新后钩子
onUpdated(() => {
  console.log('onUpdated - 组件已更新')
  console.log('当前计数:', count.value)
})
</script>

卸载阶段:onBeforeUnmount() 和 onUnmounted()

Vue3 的卸载阶段钩子名称有所变化:

html 复制代码
<template>
  <div>
    <p>计数: {{ count }}</p>
    <button @click="increment">增加</button>
  </div>
</template>

<script setup>
import { ref, onBeforeUnmount, onUnmounted } from 'vue'

const count = ref(0)

const increment = () => {
  count.value++
}

// 卸载前钩子
onBeforeUnmount(() => {
  console.log('onBeforeUnmount - 组件即将卸载')
  // 清理工作
})

// 卸载后钩子
onUnmounted(() => {
  console.log('onUnmounted - 组件已卸载')
})
</script>

Vue3 卸载阶段的变化:

  1. 名称变更beforeDestroyonBeforeUnmountdestroyedonUnmounted

  2. 更准确的语义:"unmount" 比 "destroy" 更准确地描述了组件从 DOM 中移除的过程

  3. 相同的功能:执行时机和用途与 Vue2 对应钩子相同

父子组件生命周期执行顺序

这是一个非常重要且容易混淆的概念。让我们通过示例来理解:

父组件:

html 复制代码
<template>
  <div>
    <h3>父组件</h3>
    <ChildComponent v-if="showChild" />
    <button @click="showChild = !showChild">切换子组件</button>
  </div>
</template>

<script setup>
import { ref, onMounted } from 'vue'
import ChildComponent from './ChildComponent.vue'

const showChild = ref(true)

onMounted(() => {
  console.log('父组件 onMounted')
})
</script>

子组件:

html 复制代码
<template>
  <div>
    <h4>子组件</h4>
    <p>我是子组件</p>
  </div>
</template>

<script setup>
import { onMounted } from 'vue'

onMounted(() => {
  console.log('子组件 onMounted')
})
</script>

执行结果分析:

当你运行这个示例时,控制台的输出顺序是:

html 复制代码
子组件 onMounted
父组件 onMounted

深度优先的挂载顺序:

Vue 采用深度优先的组件树遍历策略:

  1. 父组件开始挂载

  2. 遇到子组件,暂停父组件的挂载,先挂载子组件

  3. 子组件完全挂载完成后,继续完成父组件的挂载

  4. 所有子组件都挂载完成后,父组件才完成挂载

这就像建造一栋大楼:

  • 先打好地基(父组件开始)

  • 然后建造每一层楼的房间(子组件)

  • 所有房间都建好后,大楼才算建成(父组件完成)

这种深度优先的策略确保了子组件在父组件之前完全初始化,避免了父组件依赖未准备好的子组件的情况。

四、常用的生命周期钩子

在实际开发中,有些生命周期钩子使用频率很高,而有些则相对较少。

最常用的钩子

onMounted - 使用频率最高

TypeScript 复制代码
onMounted(() => {
  // DOM 操作
  // 第三方库初始化
  // API 数据获取
  // 事件监听器添加
})

onUnmounted - 资源清理

TypeScript 复制代码
onUnmounted(() => {
  // 清除定时器
  // 取消网络请求
  // 移除事件监听器
  // 清理第三方库实例
})

onUpdated - 响应数据变化

TypeScript 复制代码
onUpdated(() => {
  // 依赖于更新后 DOM 的操作
  // 跟踪特定的数据变化
})

五、总结

通过本文的深入学习,相信你已经对 Vue3 的生命周期有了全面而深刻的理解。

核心要点回顾

生命周期是组件从创建到销毁的完整过程,Vue 在关键节点提供了钩子函数,让我们能够在特定时刻执行特定代码。理解生命周期有助于我们编写更加健壮和高效的组件。

Vue2 与 Vue3 的生命周期对比

相同点:

  • 生命周期阶段划分相同:创建、挂载、更新、卸载

  • 各阶段的执行时机和用途基本相同

  • 核心概念保持一致

不同点:

  • API 形式:Vue2 是选项式,Vue3 是函数式

  • 创建阶段:Vue3 用 setup 替代 beforeCreate/created

  • 命名调整:销毁相关钩子更名为卸载相关

  • 代码组织:Vue3 提供更灵活的代码组织方式

生命周期执行顺序的关键理解

  • 深度优先:子组件总是在父组件之前完成挂载

  • 同步执行:生命周期钩子按照特定顺序同步执行

  • 可预测性:理解执行顺序有助于调试和优化

下一节我们将一起探讨Hooks。

关于 Vue3 生命周期有任何疑问?欢迎在评论区提出,我们会详细解答!

相关推荐
GISer_Jing1 小时前
SSE Conf大会分享——UTOO WASM:AI时代的浏览器原生极速研发套件
前端·人工智能·架构·wasm
Q***l6871 小时前
前端在移动端中的响应式设计
前端
QH_ShareHub1 小时前
R 包中的生命周期触发函数全解析
前端·javascript·数据库
小奶包他干奶奶1 小时前
Webpack学习——如何自定义钩子
前端·学习·webpack
AI3D_WebEngineer1 小时前
企业级业务平台项目设计、架构、业务全解之平台篇
前端·javascript·vue
该用户已不存在1 小时前
免费 SSL 证书缩短至 90 天,你的运维成本还能hold住吗
前端·后端·https
汤姆Tom1 小时前
前端转战后端:JavaScript 与 Java 对照学习指南 (第二篇 - 基本数据类型对比)
java·javascript·全栈
七月十二1 小时前
【Vite】离线打包@iconify/vue的图标
前端·vue.js
星空的资源小屋1 小时前
Explorer++:更强大的Windows文件管理器
javascript·人工智能·django·电脑