Vue 组件的生命周期钩子(完整版:创建/挂载/更新/销毁)详解

举报
William 发表于 2025/10/09 12:37:03 2025/10/09
【摘要】 一、引言在 Vue.js 的组件化开发中,​​组件的生命周期​​是理解其运行机制的核心。每个 Vue 组件从创建到销毁都会经历一系列 ​​有序的阶段​​,每个阶段都提供了特定的 ​​生命周期钩子(Lifecycle Hooks)​​,允许开发者在组件的不同状态下执行自定义逻辑(如初始化数据、操作 DOM、清理定时器等)。无论是初学者还是资深开发者,掌握生命周期钩子的使用场景与执行顺序,都是编...


一、引言

在 Vue.js 的组件化开发中,​​组件的生命周期​​是理解其运行机制的核心。每个 Vue 组件从创建到销毁都会经历一系列 ​​有序的阶段​​,每个阶段都提供了特定的 ​​生命周期钩子(Lifecycle Hooks)​​,允许开发者在组件的不同状态下执行自定义逻辑(如初始化数据、操作 DOM、清理定时器等)。
无论是初学者还是资深开发者,掌握生命周期钩子的使用场景与执行顺序,都是编写高效、可维护 Vue 应用的必备技能。本文将围绕 Vue 组件的 ​​完整生命周期(创建→挂载→更新→销毁)​​,从技术背景、应用场景、代码实现、原理解释到实战演示,全方位解析其原理与最佳实践,帮助开发者深入理解组件的“生命旅程”。

二、技术背景

1. 什么是生命周期?

Vue 组件的生命周期指的是 ​​组件从被创建到挂载到 DOM,再到数据更新、最终被销毁的整个过程​​。在这个过程中,Vue 会在特定的时间点触发一系列 ​​生命周期钩子函数​​,开发者可以通过这些钩子函数在组件的不同阶段插入自定义逻辑。

2. 生命周期的核心阶段

Vue 组件的生命周期可以分为 ​​四个主要阶段​​,每个阶段包含多个具体的钩子函数:
阶段
描述
主要钩子函数(Vue 3 Composition API)
主要钩子函数(Vue 2 Options API)
​创建阶段​
组件实例被创建,初始化数据、计算属性、方法等,但尚未挂载到 DOM。
setup()(核心入口)、onBeforeCreateonCreated
beforeCreatecreated
​挂载阶段​
组件被挂载到真实的 DOM 中,此时可以操作 DOM 元素。
onBeforeMountonMounted
beforeMountmounted
​更新阶段​
组件的数据发生变化,触发重新渲染,最终更新 DOM。
onBeforeUpdateonUpdated
beforeUpdateupdated
​销毁阶段​
组件从 DOM 中移除,释放资源(如清除定时器、取消事件监听)。
onBeforeUnmountonUnmounted
beforeDestroydestroyed
​注意​​:Vue 3 推荐使用 ​​Composition API​​(setup()函数 + onXxx钩子),但依然兼容 Options API(datamounted等选项);Vue 2 仅支持 Options API。

三、应用使用场景

1. 创建阶段(Initialization)

  • ​初始化数据​​:在组件创建时,从后端 API 获取初始数据(如用户信息、配置项);
  • ​设置全局状态​​:注册全局事件监听器(如 window.resize)或初始化第三方库(如图表库 ECharts);
  • ​验证 Props​​:在组件创建时检查传入的 Props 是否符合预期(如必填字段校验)。

2. 挂载阶段(DOM Insertion)

  • ​操作 DOM​​:在组件挂载到 DOM 后,获取 DOM 元素的引用(如通过 ref获取输入框并聚焦);
  • ​初始化第三方插件​​:如初始化地图组件(高德地图、百度地图)、富文本编辑器(Quill、TinyMCE);
  • ​触发首次数据加载​​:如发送异步请求获取列表数据(需确保 DOM 已存在)。

3. 更新阶段(Re-rendering)

  • ​响应数据变化​​:当组件的响应式数据(如 dataprops)发生变化时,执行额外的逻辑(如重新计算衍生数据);
  • ​优化性能​​:在更新前判断是否需要重新渲染(如通过 v-if控制子组件的显示/隐藏);
  • ​同步外部状态​​:如更新后同步滚动位置、重新初始化依赖 DOM 的插件。

4. 销毁阶段(Cleanup)

  • ​释放资源​​:清除定时器(如 setInterval)、取消网络请求(如 Axios 的 CancelToken)、移除事件监听器(如 window.scroll);
  • ​销毁第三方实例​​:如销毁图表实例(ECharts)、关闭 WebSocket 连接;
  • ​避免内存泄漏​​:确保组件销毁时,所有与之关联的外部资源都被正确清理。

四、不同场景下详细代码实现

场景 1:Vue 3 Composition API(完整生命周期示例)

​需求​​:封装一个用户信息卡片组件(UserInfoCard.vue),展示用户的基本信息(姓名、年龄),并在组件的不同生命周期阶段输出日志(模拟初始化数据、操作 DOM、清理定时器)。

1.1 用户信息卡片组件(UserInfoCard.vue)

<template>
  <div class="user-card">
    <h3>用户信息卡片(Vue 3 Composition API)</h3>
    <p><strong>姓名:</strong>{{ user.name }}</p>
    <p><strong>年龄:</strong>{{ user.age }}</p>
    <button @click="updateAge">年龄 +1(触发更新)</button>
  </div>
</template>

<script setup>
import { ref, onBeforeCreate, onCreate, onBeforeMount, onMounted, onBeforeUpdate, onUpdated, onBeforeUnmount, onUnmounted } from 'vue';

// 响应式数据
const user = ref({ name: '张三', age: 25 });
let timer = null; // 定时器引用(用于销毁阶段清理)

// --- 创建阶段 ---
onBeforeCreate(() => {
  console.log('🔹 onBeforeCreate:组件实例刚被创建,data 和 methods 未初始化');
});

onCreate(() => {
  console.log('🔹 onCreate:组件实例已创建,data 和 methods 已初始化(但未挂载到 DOM)');
  // 模拟初始化数据(如从后端获取用户信息)
  setTimeout(() => {
    user.value.name = '李四'; // 修改初始数据(但此时还未挂载,DOM 不可见)
  }, 500);
});

// --- 挂载阶段 ---
onBeforeMount(() => {
  console.log('🔹 onBeforeMount:组件即将挂载到 DOM(template 已编译,但未插入真实 DOM)');
});

onMounted(() => {
  console.log('🔹 onMounted:组件已挂载到 DOM(可以操作 DOM 元素或初始化第三方插件)');
  // 操作 DOM:获取输入框焦点(示例)
  console.log('✅ DOM 已挂载,当前用户年龄:', user.value.age);
  // 模拟启动定时器(如轮询数据)
  timer = setInterval(() => {
    console.log('🔄 定时器运行中... 当前年龄:', user.value.age);
  }, 2000);
});

// --- 更新阶段 ---
onBeforeUpdate(() => {
  console.log('🔹 onBeforeUpdate:组件数据即将更新(DOM 未重新渲染)');
});

onUpdated(() => {
  console.log('🔹 onUpdated:组件数据已更新,DOM 已重新渲染');
});

// --- 销毁阶段 ---
onBeforeUnmount(() => {
  console.log('🔹 onBeforeUnmount:组件即将被销毁(清理定时器、事件监听器等)');
  if (timer) {
    clearInterval(timer); // 清理定时器(避免内存泄漏)
    timer = null;
  }
});

onUnmounted(() => {
  console.log('🔹 onUnmounted:组件已销毁(从 DOM 中移除)');
});

// 方法:更新用户年龄(触发更新阶段)
const updateAge = () => {
  user.value.age += 1;
};
</script>

<style scoped>
.user-card {
  padding: 16px;
  border: 1px solid #eee;
  border-radius: 8px;
  margin: 10px;
}
button {
  margin-top: 10px;
  padding: 8px 16px;
  background: #007bff;
  color: white;
  border: none;
  border-radius: 4px;
  cursor: pointer;
}
</style>

1.2 父组件(App.vue)

<template>
  <div id="app">
    <h1>Vue 3 组件生命周期钩子示例</h1>
    <UserInfoCard />
    <button @click="destroyComponent">销毁组件(模拟卸载)</button>
  </div>
</template>

<script setup>
import { ref } from 'vue';
import UserInfoCard from './components/UserInfoCard.vue';

const showComponent = ref(true);

const destroyComponent = () => {
  showComponent.value = false; // 通过 v-if 控制组件销毁(实际项目中可能是路由切换等场景)
};

// 注意:这里简化了销毁逻辑,实际 UserInfoCard 通过 v-if 控制显示/隐藏
// 为了演示销毁钩子,需将 UserInfoCard 包裹在 v-if 中(代码略,可自行扩展)
</script>

<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  padding: 20px;
  max-width: 500px;
  margin: 0 auto;
}
</style>
​运行结果​​:
  • 打开浏览器控制台(F12),观察组件在不同阶段输出的日志(如 onBeforeCreateonMountedonUpdated);
  • 点击“年龄 +1”按钮,触发更新阶段的钩子(onBeforeUpdateonUpdated);
  • 点击“销毁组件”按钮(需扩展代码,如通过 v-if控制 UserInfoCard的显示),观察销毁阶段的钩子(onBeforeUnmountonUnmounted)。

场景 2:Vue 2 Options API(经典生命周期示例)

​需求​​:封装一个计数器组件(Counter.vue),通过 Options API 的生命周期钩子(如 createdmountedupdated)实现数据初始化、DOM 操作和更新逻辑。

2.1 计数器组件(Counter.vue)

<template>
  <div class="counter">
    <h3>计数器(Vue 2 Options API)</h3>
    <p>当前计数:{{ count }}</p>
    <button @click="increment">+1(触发更新)</button>
  </div>
</template>

<script>
export default {
  name: 'Counter',
  data() {
    return {
      count: 0,
    };
  },
  beforeCreate() {
    console.log('🔹 beforeCreate:组件实例刚被创建,data 和 methods 未初始化');
  },
  created() {
    console.log('🔹 created:组件实例已创建,data 和 methods 已初始化(但未挂载到 DOM)');
    // 初始化数据(如从后端获取初始计数)
    this.count = parseInt(localStorage.getItem('counter') || '0', 10);
  },
  beforeMount() {
    console.log('🔹 beforeMount:组件即将挂载到 DOM');
  },
  mounted() {
    console.log('🔹 mounted:组件已挂载到 DOM(可以操作 DOM 或初始化插件)');
    // 操作 DOM:获取按钮引用(示例)
    console.log('✅ DOM 已挂载,当前计数:', this.count);
    // 模拟从本地存储加载数据
    if (localStorage.getItem('counter')) {
      console.log('📦 已加载本地存储的计数:', this.count);
    }
  },
  beforeUpdate() {
    console.log('🔹 beforeUpdate:组件数据即将更新(DOM 未重新渲染)');
  },
  updated() {
    console.log('🔹 updated:组件数据已更新,DOM 已重新渲染');
    // 更新后同步到本地存储
    localStorage.setItem('counter', this.count.toString());
  },
  beforeDestroy() {
    console.log('🔹 beforeDestroy:组件即将被销毁(清理资源)');
    // 清理逻辑(如移除事件监听器)
  },
  destroyed() {
    console.log('🔹 destroyed:组件已销毁(从 DOM 中移除)');
  },
  methods: {
    increment() {
      this.count += 1;
    },
  },
};
</script>

<style scoped>
.counter {
  padding: 16px;
  border: 1px solid #eee;
  border-radius: 8px;
  margin: 10px;
}
button {
  margin-top: 10px;
  padding: 8px 16px;
  background: #28a745;
  color: white;
  border: none;
  border-radius: 4px;
  cursor: pointer;
}
</style>

2.2 父组件(App.vue)

<template>
  <div id="app">
    <h1>Vue 2 组件生命周期钩子示例</h1>
    <Counter />
  </div>
</template>

<script>
import Counter from './components/Counter.vue';

export default {
  name: 'App',
  components: { Counter },
};
</script>

<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  padding: 20px;
  max-width: 500px;
  margin: 0 auto;
}
</style>
​运行结果​​:
  • 打开控制台,观察计数器组件在不同阶段的日志(如 created中初始化数据、mounted中操作 DOM、updated中同步数据到本地存储);
  • 点击“+1”按钮,触发更新阶段的钩子(beforeUpdateupdated),并看到计数持久化到本地存储。

五、原理解释

1. 生命周期的核心机制

Vue 组件的生命周期是由 Vue 内部引擎控制的 ​​有序流程​​,每个阶段对应特定的钩子函数,开发者可以通过这些钩子在组件的不同状态下执行逻辑。其核心原理如下:
  1. ​创建阶段(Creation)​​:
    • Vue 首先调用 beforeCreate,此时组件实例刚被创建,但 datamethods等选项尚未初始化;
    • 接着执行 created,此时组件的响应式数据(data)、计算属性(computed)、方法(methods)已初始化完成,但组件还未挂载到 DOM。
  2. ​挂载阶段(Mounting)​​:
    • Vue 调用 beforeMount,此时组件的模板已编译成渲染函数,但尚未将生成的虚拟 DOM 插入到真实 DOM 中;
    • 随后执行 mounted,此时组件已挂载到 DOM(可通过 this.$el访问根 DOM 元素),可以安全地操作 DOM 或初始化依赖 DOM 的第三方库。
  3. ​更新阶段(Updating)​​:
    • 当组件的响应式数据(如 dataprops)发生变化时,Vue 会先调用 beforeUpdate,此时新的虚拟 DOM 已生成,但还未替换旧的 DOM;
    • 接着重新渲染组件并生成新的虚拟 DOM,最后调用 updated,此时 DOM 已更新为最新状态。
  4. ​销毁阶段(Destruction)​​:
    • 当组件被销毁(如通过 v-if控制为 false或路由切换)时,Vue 先调用 beforeUnmount(Vue 3)/ beforeDestroy(Vue 2),此时可以清理定时器、取消事件监听器、销毁第三方实例;
    • 最后调用 unmounted(Vue 3)/ destroyed(Vue 2),组件已从 DOM 中移除,所有关联的资源应已被清理。

2. Vue 3 与 Vue 2 的钩子差异

特性
Vue 3(Composition API)
Vue 2(Options API)
​钩子函数定义​
通过 onBeforeCreateonMounted等函数定义
通过 beforeCreatemounted等选项定义
​核心入口​
setup()函数(在 beforeCreate之前执行)
datacreated等选项
​响应式数据​
使用 refreactive等组合式 API 管理
使用 data函数返回对象
​代码组织​
逻辑更集中(相关逻辑写在 setup中)
逻辑分散在各个选项(如 datamethods
​注意​​:Vue 3 的 setup()函数在 beforeCreate之前执行,因此在 setup中无法访问 this(组件实例未创建),但可以直接使用 refreactive定义响应式数据。

六、核心特性

特性
说明
​有序性​
生命周期钩子按照固定的顺序触发(创建→挂载→更新→销毁),不可颠倒;
​阶段性​
每个阶段(创建、挂载、更新、销毁)包含多个钩子,覆盖组件的全生命周期;
​灵活性​
开发者可根据需求选择在特定阶段执行逻辑(如只在挂载后操作 DOM);
​响应式集成​
更新阶段的钩子与 Vue 的响应式系统深度集成,自动响应数据变化;
​资源管理​
销毁阶段的钩子用于清理定时器、事件监听器等,避免内存泄漏;
​Vue 3 优化​
Composition API 的 setup()函数提供了更灵活的逻辑组织和复用能力;

七、原理流程图及原理解释

原理流程图(Vue 3 组件生命周期)

+-----------------------+
|     组件创建          |
|  (beforeCreate)       |  --> 组件实例刚创建,data/methods 未初始化
+-----------------------+
          |
          v
+-----------------------+
|     组件创建完成      |
|  (created)            |  --> data/methods 已初始化,但未挂载到 DOM
+-----------------------+
          |
          v
+-----------------------+
|     即将挂载 DOM      |
|  (beforeMount)        |  --> 模板已编译,但未插入真实 DOM
+-----------------------+
          |
          v
+-----------------------+
|     已挂载到 DOM      |
|  (mounted)            |  --> 组件已渲染到页面,可操作 DOM
+-----------------------+
          |
          v
+-----------------------+  <-- 数据变化触发更新
|     即将更新 DOM      |
|  (beforeUpdate)       |  --> 新的虚拟 DOM 已生成,但未替换旧 DOM
+-----------------------+
          |
          v
+-----------------------+
|     已更新 DOM        |
|  (updated)            |  --> DOM 已同步为最新状态
+-----------------------+
          |
          v
+-----------------------+  <-- 组件被销毁(如 v-if=false)
|     即将销毁          |
|  (beforeUnmount)      |  --> 清理定时器、事件监听器
+-----------------------+
          |
          v
+-----------------------+
|     已销毁            |
|  (unmounted)          |  --> 组件从 DOM 中移除
+-----------------------+

原理解释

  1. ​创建阶段​​:Vue 首先执行 beforeCreate(此时组件实例刚生成,但 datamethods未初始化),随后执行 created(此时所有响应式数据和方法已就绪,但组件还未挂载到 DOM)。
  2. ​挂载阶段​​:接着执行 beforeMount(模板已编译成渲染函数,但虚拟 DOM 未插入真实 DOM),最后执行 mounted(组件已挂载到页面,可通过 this.$elref访问 DOM 元素)。
  3. ​更新阶段​​:当响应式数据(如用户点击按钮修改 count)变化时,Vue 重新计算虚拟 DOM,先执行 beforeUpdate(新的虚拟 DOM 已生成),然后更新 DOM 并执行 updated(DOM 已同步为最新状态)。
  4. ​销毁阶段​​:当组件被移除(如路由切换或 v-if变为 false),先执行 beforeUnmount(清理定时器、取消事件监听),最后执行 unmounted(组件完全从 DOM 中移除,所有资源已释放)。

八、环境准备

1. 开发环境

  • ​Vue 2 或 Vue 3​​:生命周期钩子在两个版本中均支持,但 Vue 3 推荐使用 Composition API(setup()+ onXxx);
  • ​开发工具​​:Vue CLI 或 Vite(用于快速创建项目);
  • ​浏览器​​:Chrome/Firefox(用于查看控制台日志)。

2. 项目配置

  • 确保项目的 Vue 版本正确(通过 package.json检查 vue依赖版本);
  • 若使用 Vue 3 的 Composition API,需熟悉 <script setup>语法或 setup()函数的定义方式。

九、实际详细应用代码示例实现

完整项目代码(整合上述场景)

1. Vue 3 组件(UserInfoCard.vue)

(代码同场景 1)

2. Vue 2 组件(Counter.vue)

(代码同场景 2)

3. 主应用(App.vue)

<template>
  <div id="app">
    <h1>Vue 组件生命周期钩子完整示例</h1>
    
    <!-- Vue 3 组件示例 -->
    <section>
      <h2>Vue 3:用户信息卡片(Composition API)</h2>
      <UserInfoCard />
    </section>

    <!-- Vue 2 组件示例 -->
    <section>
      <h2>Vue 2:计数器(Options API)</h2>
      <Counter />
    </section>
  </div>
</template>

<script setup>
import UserInfoCard from './components/UserInfoCard.vue';
import Counter from './components/Counter.vue';
</script>

<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  padding: 20px;
  max-width: 800px;
  margin: 0 auto;
}
section {
  margin-bottom: 40px;
  border: 1px solid #eee;
  padding: 20px;
  border-radius: 8px;
}
</style>
​运行结果​​:
  • 打开控制台,分别观察 Vue 3 组件(UserInfoCard)和 Vue 2 组件(Counter)在不同生命周期阶段输出的日志;
  • 通过交互(如点击按钮更新数据)验证更新阶段的钩子触发逻辑。

十、运行结果

1. Vue 3 组件的表现

  • 控制台输出创建阶段(beforeCreatecreated)、挂载阶段(beforeMountmounted)、更新阶段(beforeUpdateupdated)、销毁阶段(beforeUnmountunmounted)的日志;
  • 点击“年龄 +1”按钮,触发更新阶段的钩子,同时定时器每 2 秒输出当前年龄(模拟数据变化)。

2. Vue 2 组件的表现

  • 控制台输出经典生命周期钩子的日志(如 beforeCreatecreatedmounted),点击“+1”按钮触发 beforeUpdateupdated,并同步数据到本地存储。

十一、测试步骤以及详细代码

1. 测试目标

验证生命周期钩子的核心功能,包括:
  • 组件在不同阶段(创建、挂载、更新、销毁)是否正确触发对应的钩子;
  • 更新阶段的钩子是否在数据变化时被触发;
  • 销毁阶段的钩子是否清理了定时器等资源。

2. 测试步骤

步骤 1:启动项目

npm run serve  # Vue 2
# 或 npm run dev  # Vue 3 (Vite)
访问 http://localhost:5173(Vite 默认端口),查看 Vue 3 和 Vue 2 组件的示例。

步骤 2:测试创建与挂载阶段

  • 打开浏览器控制台,观察组件初始化时输出的日志(如 Vue 3 的 beforeCreatecreatedbeforeMountmounted);
  • 确认组件是否在挂载后正确显示(如用户信息卡片、计数器初始值)。

步骤 3:测试更新阶段

  • 点击 Vue 3 组件的“年龄 +1”按钮或 Vue 2 组件的“+1”按钮,观察控制台输出的 beforeUpdateupdated日志;
  • 确认组件数据和 DOM 是否同步更新(如年龄数字增加、计数器值变化)。

步骤 4:测试销毁阶段

  • 对于 Vue 3 组件,点击“销毁组件”按钮(需扩展代码,如通过 v-if控制显示),观察 beforeUnmountunmounted日志;
  • 对于 Vue 2 组件,通过路由切换或其他方式销毁组件,确认定时器是否被清理(避免内存泄漏)。

十二、部署场景

1. 生产环境注意事项

  • ​资源清理​​:确保在销毁阶段(beforeUnmount/destroyed)清理所有定时器、事件监听器和第三方实例,避免内存泄漏;
  • ​性能优化​​:在更新阶段(beforeUpdate)可通过逻辑判断减少不必要的重新渲染(如使用 v-if控制子组件的显示);
  • ​错误监控​​:在创建阶段(created)或挂载阶段(mounted)添加错误边界逻辑(如捕获异步请求的异常)。

2. 适用场景

  • ​初始化逻辑​​:如从后端加载初始数据、注册全局事件监听器;
  • ​DOM 操作​​:如组件挂载后获取元素引用、初始化图表库;
  • ​数据同步​​:如更新后同步状态到本地存储或后端;
  • ​资源管理​​:如销毁时清理定时器、关闭 WebSocket 连接。

十三、疑难解答

1. 问题 1:生命周期钩子未触发?

​可能原因​​:
  • Vue 3 中使用了错误的钩子函数(如拼写错误,应为 onMounted而非 mounted);
  • 组件未被正确挂载(如通过 v-if="false"永久隐藏,导致 mounted不触发);
  • Vue 2 中 Options API 的钩子选项拼写错误(如 beforeMount写成 `
【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。