当前位置: 首页 > news >正文

Vue 生命周期全解析:从创建到销毁的完整旅程

Vue 生命周期是每个 Vue 开发者必须深入理解的核心概念之一。它定义了组件从创建、挂载、更新、销毁的整个过程,以及在这个过程中各个阶段提供的钩子函数。掌握生命周期不仅能帮助你理解 Vue 的工作原理,还能让你在合适的时机执行特定的操作,优化应用性能,避免常见陷阱。本文将从源码实现到实际应用,全面解析 Vue 生命周期的各个阶段。

一、生命周期概览

Vue 组件的生命周期可以分为四个主要阶段:

  1. 初始化与挂载:创建组件实例,初始化数据,挂载 DOM
  2. 数据更新:响应式数据变更时触发更新流程
  3. 销毁:清理组件并释放资源
  4. 特殊场景:错误处理、服务器端渲染等

每个阶段都提供了相应的钩子函数,开发者可以在这些钩子中注入自定义逻辑。生命周期钩子的执行顺序是固定的,理解这个顺序对于编写正确的代码至关重要。

二、生命周期阶段详解

2.1 初始化与挂载阶段

这个阶段从组件实例创建开始,到 DOM 挂载完成结束。

2.1.1 beforeCreate
  • 触发时机:实例初始化之后,数据观测 (data observer) 和 event/watcher 事件配置之前被调用。
  • 特性
    • 此时 this 指向实例,但数据和方法均未初始化。
    • 无法访问 datamethodscomputed
    • 通常用于初始化非响应式数据或全局插件。
  • 示例
    export default {beforeCreate() {// 初始化全局事件总线this.$bus = new Vue();// 记录组件创建时间this._createdAt = Date.now();}
    }
    
2.1.2 created
  • 触发时机:实例已经创建完成之后被调用。在这一步,实例已经完成了数据观测、propertymethod 的计算、watch/event 事件回调的配置。然而,挂载阶段还没有开始,$el 属性目前不可用。
  • 特性
    • 可以访问 datamethodscomputed,但 DOM 尚未挂载。
    • 适合进行数据获取(如 API 请求)或初始化依赖数据的操作。
  • 示例
    export default {data() {return {users: []};},async created() {try {const response = await fetch('/api/users');this.users = await response.json();} catch (error) {console.error('Failed to fetch users', error);}}
    }
    
2.1.3 beforeMount
  • 触发时机:挂载开始之前被调用。
  • 特性
    • 模板编译/渲染函数已经完成,但尚未挂载到 DOM。
    • $el 是虚拟 DOM,不可访问实际 DOM 元素。
    • 适合在渲染前对模板进行最后的修改。
  • 源码关键点
    // Vue 源码简化版
    vm.$el = vm.$options.el;
    callHook(vm, 'beforeMount');// 编译模板生成 render 函数
    const updateComponent = () => {vm._update(vm._render(), hydrating);
    };
    
2.1.4 mounted
  • 触发时机:挂载完成后被调用。此时模板已经编译完成并挂载到 DOM 上。
  • 特性
    • 可以访问 $el 和实际 DOM 元素。
    • 子组件已经完成挂载(但不保证所有异步子组件都已完成)。
    • 适合进行 DOM 操作、初始化第三方插件(如 Chart.js、Leaflet)或订阅事件。
  • 示例
    export default {mounted() {// 初始化图表this.chart = new Chart(this.$el.querySelector('#chart'), {type: 'bar',data: this.chartData});// 添加 DOM 事件监听器this.$el.addEventListener('click', this.handleClick);},beforeDestroy() {// 清理图表实例和事件监听器this.chart.destroy();this.$el.removeEventListener('click', this.handleClick);}
    }
    
2.2 数据更新阶段

这个阶段在组件数据发生变化时触发,包含虚拟 DOM 重新渲染和打补丁的过程。

2.2.1 beforeUpdate
  • 触发时机:数据更新时调用,发生在虚拟 DOM 打补丁之前。
  • 特性
    • 数据已经变更,但 DOM 尚未更新。
    • 可以访问更新前的 DOM 状态。
    • 适合在更新前保存当前 DOM 状态或执行一些预处理。
  • 示例
    export default {data() {return {list: [1, 2, 3]};},beforeUpdate() {// 保存更新前的列表高度this.prevListHeight = this.$el.offsetHeight;}
    }
    
2.2.2 updated
  • 触发时机:由于数据更改导致的虚拟 DOM 重新渲染和打补丁之后调用。
  • 特性
    • 数据和 DOM 都已经更新。
    • 可以访问更新后的 DOM 状态。
    • 注意:不要在这个钩子中修改数据,否则可能导致无限循环更新。
  • 示例
    export default {updated() {// 对比更新前后的列表高度,执行动画if (this.prevListHeight !== this.$el.offsetHeight) {this.animateListHeightChange();}}
    }
    
2.3 销毁阶段

这个阶段在组件实例销毁时触发,用于清理资源和事件监听器。

2.3.1 beforeDestroy (Vue 2) / beforeUnmount (Vue 3)
  • 触发时机:实例销毁之前调用。此时实例仍然完全可用。
  • 特性
    • 组件仍然完全正常工作。
    • 适合进行资源清理(如定时器、事件监听器、WebSocket 连接等)。
  • 示例
    export default {created() {this.timer = setInterval(() => {console.log('定时任务');}, 1000);this.$bus.$on('some-event', this.handleEvent);},beforeDestroy() {// 清理定时器clearInterval(this.timer);// 取消事件订阅this.$bus.$off('some-event', this.handleEvent);}
    }
    
2.3.2 destroyed (Vue 2) / unmounted (Vue 3)
  • 触发时机:实例已经完全销毁之后调用。
  • 特性
    • 所有的事件监听器和子实例已经被销毁。
    • 组件实例完全不可用。
    • 通常用于确认资源是否已经正确释放。
  • 源码关键点
    // Vue 源码简化版
    callHook(vm, 'beforeDestroy');// 递归销毁子组件
    vm.$children.forEach(child => {child.$destroy();
    });// 移除所有事件监听器
    vm._events = Object.create(null);callHook(vm, 'destroyed');
    
2.4 特殊场景钩子
2.4.1 activated / deactivated
  • 触发时机
    • activated:被 <keep-alive> 缓存的组件激活时调用。
    • deactivated:被 <keep-alive> 缓存的组件停用时调用。
  • 特性
    • 只在使用 <keep-alive> 包裹的组件中触发。
    • 适合处理缓存组件的特殊逻辑(如恢复滚动位置、刷新数据)。
  • 示例
    <keep-alive><router-view />
    </keep-alive>
    
    export default {activated() {// 组件被激活时刷新数据this.fetchData();},deactivated() {// 保存组件状态this.saveScrollPosition();}
    }
    
2.4.2 errorCaptured (Vue 2) / errorCaptured + renderTracked + renderTriggered (Vue 3)
  • 触发时机
    • errorCaptured:捕获来自子孙组件的错误时调用。
    • renderTracked / renderTriggered(Vue 3):用于调试响应式依赖的追踪和触发。
  • 特性
    • 可以阻止错误继续向上传播。
    • 适合实现全局错误处理或日志记录。
  • 示例
    export default {errorCaptured(err, vm, info) {// 记录错误日志console.error('Error captured:', err, info);// 可以返回 false 阻止错误继续向上传播return false;}
    }
    
2.4.3 serverPrefetch (Vue 3 仅 SSR)
  • 触发时机:在服务器端渲染(SSR)期间,组件实例在服务器上被创建时调用。
  • 特性
    • 仅在 SSR 模式下有效。
    • 用于在服务器端预取数据,避免客户端重复请求。
  • 示例
    export default {async serverPrefetch() {// 在服务器端预取数据this.data = await fetchData();}
    }
    

三、生命周期流程图与执行顺序

3.1 Vue 2 生命周期流程图
创建实例↓
beforeCreate↓
初始化 data/methods↓
created↓
是否有 el 选项?↓├─ 否 → 等待 vm.$mount(el)↓├─ 是 → 是否有 template 选项?↓├─ 是 → 编译 template 为 render 函数↓└─ 否 → 使用 el 的 outerHTML 作为 template↓
beforeMount↓
创建 vm.$el 并替换 el↓
mounted↓
数据变更↓
beforeUpdate↓
虚拟 DOM 重新渲染 & 打补丁↓
updated↓
调用 vm.$destroy()↓
beforeDestroy↓
销毁所有子实例、事件监听器和子组件↓
destroyed
3.2 Vue 3 生命周期变更

Vue 3 对生命周期钩子进行了一些重命名,以更准确地反映其用途:

  • beforeDestroybeforeUnmount
  • destroyedunmounted

新增钩子:

  • setup():替代 beforeCreatecreated,是 Composition API 的入口点。
  • renderTracked / renderTriggered:用于调试响应式依赖。

四、生命周期钩子的实际应用场景

4.1 数据获取
  • 最佳位置createdmounted
  • 选择依据
    • 如果数据获取不依赖 DOM 操作,使用 created(稍早执行)。
    • 如果需要访问 DOM 元素,使用 mounted
  • 示例
    export default {data() {return {posts: [],loading: true,error: null};},async created() {try {const response = await fetch('/api/posts');this.posts = await response.json();} catch (error) {this.error = error.message;} finally {this.loading = false;}}
    }
    
4.2 DOM 操作与第三方插件集成
  • 最佳位置mounted
  • 示例:初始化 Chart.js 图表
    export default {mounted() {const ctx = this.$el.querySelector('#myChart').getContext('2d');this.chart = new Chart(ctx, {type: 'line',data: this.chartData,options: this.chartOptions});},beforeUnmount() {// 销毁图表实例this.chart.destroy();}
    }
    
4.3 状态恢复与保存
  • 最佳位置activated / deactivated(配合 <keep-alive>
  • 示例:保存和恢复滚动位置
    export default {data() {return {scrollPosition: 0};},deactivated() {// 保存当前滚动位置this.scrollPosition = window.scrollY;},activated() {// 恢复滚动位置window.scrollTo(0, this.scrollPosition);}
    }
    
4.4 资源清理
  • 最佳位置beforeDestroy / beforeUnmount
  • 示例:清理定时器、取消订阅、关闭网络连接
    export default {created() {this.socket = new WebSocket('ws://example.com');this.interval = setInterval(this.updateData, 5000);},beforeUnmount() {// 清理 WebSocket 连接this.socket.close();// 清除定时器clearInterval(this.interval);}
    }
    
4.5 全局状态初始化
  • 最佳位置beforeCreate
  • 示例:初始化全局事件总线或配置
    export default {beforeCreate() {// 初始化全局事件总线this.$bus = new Vue();// 配置全局 API 基地址this.$apiBaseUrl = process.env.VUE_APP_API_BASE_URL;}
    }
    

五、生命周期钩子的性能考虑

5.1 避免在钩子中执行耗时操作
  • 问题:在 mountedupdated 等钩子中执行大量计算或同步 API 请求会阻塞 UI 渲染。
  • 解决方案
    • 使用异步操作(如 async/await)处理 API 请求。
    • 将复杂计算移至 computed 属性或 watch 中。
    export default {async mounted() {// 错误:同步执行大量计算// this.result = heavyCalculation(this.data);// 正确:异步执行setTimeout(() => {this.result = heavyCalculation(this.data);}, 0);// 或使用 Web Workerthis.worker.postMessage(this.data);this.worker.onmessage = (e) => {this.result = e.data;};}
    }
    
5.2 避免在 updated 中修改数据
  • 问题:在 updated 中修改数据会触发新的更新周期,可能导致无限循环。
  • 解决方案
    • 仅在数据满足特定条件时才修改,且确保不会再次触发更新。
    export default {updated() {// 错误:可能导致无限循环// if (this.value < 10) this.value++;// 正确:使用 nextTick 避免立即触发更新if (this.value < 10 && !this.updating) {this.updating = true;this.$nextTick(() => {this.value++;this.updating = false;});}}
    }
    
5.3 合理使用生命周期钩子
  • 问题:在不需要的钩子中添加逻辑会增加组件复杂度和执行时间。
  • 解决方案
    • 只在真正需要的钩子中添加代码。
    • 使用 Composition API 将相关逻辑组织在一起,减少对生命周期钩子的依赖。

六、Vue 3 Composition API 中的生命周期

Vue 3 的 Composition API 提供了与生命周期钩子等效的函数,使逻辑复用更加灵活:

6.1 等效钩子映射
选项式 APIComposition API
beforeCreatesetup()
createdsetup()
beforeMountonBeforeMount
mountedonMounted
beforeUpdateonBeforeUpdate
updatedonUpdated
beforeDestroyonBeforeUnmount
destroyedonUnmounted
errorCapturedonErrorCaptured
renderTrackedonRenderTracked (仅开发模式)
renderTriggeredonRenderTriggered (仅开发模式)
6.2 示例:使用 Composition API 访问生命周期
import { onMounted, onBeforeUnmount, ref } from 'vue';export default {setup() {const count = ref(0);let timer;// 等效于 mountedonMounted(() => {timer = setInterval(() => {count.value++;}, 1000);});// 等效于 beforeUnmountonBeforeUnmount(() => {clearInterval(timer);});return {count};}
};
6.3 Composition API 的优势
  • 逻辑复用:可以将相关生命周期逻辑封装到可复用的函数中。
  • 代码组织:将同一功能的代码集中在一起,提高可读性。
  • 类型安全:更好地支持 TypeScript,提供更准确的类型推导。

七、生命周期钩子的常见误区与解决方案

7.1 误区:在 mounted 中直接操作子组件 DOM
  • 问题:子组件可能尚未完全挂载,直接访问子组件 ref 会失败。
  • 解决方案
    • 使用 nextTick 确保子组件已挂载。
    • 使用事件或 props 进行组件间通信。
    export default {mounted() {// 错误:子组件可能尚未挂载// this.$refs.child.doSomething();// 正确:使用 nextTickthis.$nextTick(() => {this.$refs.child.doSomething();});}
    }
    
7.2 误区:在 destroyed 中访问组件实例
  • 问题:在 destroyed 钩子中,组件实例已经完全销毁,访问 this 可能导致错误。
  • 解决方案
    • beforeDestroy 中进行所有清理操作。
    export default {beforeDestroy() {// 正确:此时组件仍然可用this.cleanupResources();},destroyed() {// 错误:不要在这里访问 this// this.cleanupResources(); // 可能导致错误}
    }
    
7.3 误区:过度使用生命周期钩子
  • 问题:在多个生命周期钩子中重复相同的逻辑,导致代码冗余。
  • 解决方案
    • 使用 Composition API 将相关逻辑封装到一个函数中。
    • 使用 watchcomputed 处理数据变化逻辑。
    // 坏:重复逻辑
    export default {mounted() {this.initData();},activated() {this.initData();},methods: {initData() {// 初始化逻辑}}
    };// 好:使用 Composition API 封装
    import { onMounted, onActivated } from 'vue';export function useInitData(initFn) {onMounted(initFn);onActivated(initFn);
    }// 在组件中使用
    export default {setup() {useInitData(() => {// 初始化逻辑});}
    };
    

八、生命周期源码解析(简化版)

Vue 的生命周期实现主要涉及以下几个核心模块:

  1. 实例初始化src/core/instance/init.js
  2. 生命周期钩子src/core/instance/lifecycle.js
  3. 渲染流程src/core/instance/render.js
  4. 更新流程src/core/observer/watcher.js
8.1 关键源码片段
// src/core/instance/init.js
Vue.prototype._init = function(options) {const vm = this;// 初始化生命周期状态initLifecycle(vm);// 初始化事件系统initEvents(vm);// 初始化渲染initRender(vm);// 调用 beforeCreate 钩子callHook(vm, 'beforeCreate');// 初始化注入initInjections(vm);// 初始化 data、props、computed 等initState(vm);// 初始化 provideinitProvide(vm);// 调用 created 钩子callHook(vm, 'created');if (vm.$options.el) {// 挂载组件vm.$mount(vm.$options.el);}
};// src/core/instance/lifecycle.js
Vue.prototype.$mount = function(el) {// 编译模板并生成 render 函数const mount = Vue.prototype._mount;const vm = this;// 调用 beforeMount 钩子callHook(vm, 'beforeMount');// 执行渲染const vnode = vm._render();vm._update(vnode, hydrating);// 调用 mounted 钩子callHook(vm, 'mounted');return vm;
};// 数据更新触发的更新流程
Watcher.prototype.update = function() {const vm = this.vm;// 调用 beforeUpdate 钩子callHook(vm, 'beforeUpdate');// 执行虚拟 DOM 更新vm._update(vm._render(), hydrating);// 调用 updated 钩子callHook(vm, 'updated');
};// 组件销毁流程
Vue.prototype.$destroy = function() {const vm = this;// 调用 beforeDestroy 钩子callHook(vm, 'beforeDestroy');// 执行销毁操作:移除事件监听器、销毁子组件等vm._isBeingDestroyed = true;// 递归销毁子组件vm.$children.forEach(child => {child.$destroy();});// 移除所有事件监听器vm._events = Object.create(null);// 调用 destroyed 钩子callHook(vm, 'destroyed');vm._isDestroyed = true;
};

九、总结与最佳实践

9.1 生命周期关键要点总结
  1. 初始化与挂载

    • beforeCreate:实例初始化后,数据和事件系统尚未初始化。
    • created:数据观测、propertymethod 计算完成,可进行数据获取。
    • beforeMount:模板编译完成,但尚未挂载到 DOM。
    • mounted:DOM 挂载完成,可进行 DOM 操作和第三方插件初始化。
  2. 数据更新

    • beforeUpdate:数据变更后,DOM 更新前。
    • updated:DOM 更新完成,避免在此修改数据。
  3. 销毁阶段

    • beforeDestroy/beforeUnmount:实例销毁前,可进行资源清理。
    • destroyed/unmounted:实例完全销毁,不可访问组件状态。
  4. 特殊场景

    • activated/deactivated<keep-alive> 缓存组件的激活/停用。
    • errorCaptured:捕获子孙组件的错误。
9.2 最佳实践建议
  1. 数据获取:优先在 created 中进行,避免阻塞 DOM 渲染。
  2. DOM 操作:仅在 mounted 或之后进行,确保 DOM 已渲染。
  3. 资源清理:在 beforeDestroy/beforeUnmount 中清理定时器、事件监听器等。
  4. 避免重复逻辑:使用 Composition API 或 mixins 复用生命周期相关逻辑。
  5. 调试工具:利用 Vue DevTools 监控生命周期钩子的执行情况。
  6. 性能优化:避免在生命周期钩子中执行耗时操作,使用异步处理。

掌握 Vue 生命周期是成为一名优秀 Vue 开发者的基础。通过合理利用生命周期钩子,你可以更精确地控制组件行为,优化应用性能,避免常见的开发陷阱。在实际开发中,结合 Composition API 的强大功能,你可以更灵活地组织和复用代码,打造出高质量的 Vue 应用。

相关文章:

  • [网页五子棋][匹配模块]实现胜负判定,处理玩家掉线
  • 测试面试题 手机号验证码登录测试用例
  • 论文导读 | 动态图存储与事务处理系统总结
  • 敏捷开发中如何避免过度加班
  • 代码随想录 算法训练 Day22:回溯算法part01
  • AIGC 基础篇 高等数学篇 03 中值定理与导数应用
  • 大数据学习(130)-zookeeper
  • Linux系统-基本指令(6)
  • 幂等性:保障系统稳定的关键设计
  • C++内联函数(inline)的作用
  • BUU MISC(持续更新)
  • Linux容器篇、第一章docker命令总结表
  • NLP学习路线图(二十二): 循环神经网络(RNN)
  • 【Python指南】离线安装顽固复杂的第三方库指南
  • 嵌入式系统中常用的开源协议
  • (1-6-3)Java 多线程
  • 深度解析ArrayList
  • Java DLL依赖缺失解决思路和修复过程(Windows版本)
  • django paramiko 跳转登录
  • C++ 使用 ffmpeg 解码本地视频并获取每帧的YUV数据
  • 福州做网站建设/上海网站建设方案
  • 企业网站cms模板/中视频自媒体平台注册
  • 株洲做网站公司/武汉seo优
  • 校园网站建设提升/论坛推广网站
  • 最便宜的网站建设公司/关键词优化排名公司
  • 领地免费网站开发/最新seo视频教程