简单介绍下 Vue 2.x 中的几种生命周期钩子(Lifecycle Hooks)

笔记哥 / 05-22 / 35点赞 / 0评论 / 370阅读
## 〇、前言 在使用 Element UI 框架(基于 Vue 2.x)开发应用时,理解 Vue 的生命周期钩子(Lifecycle Hooks)是非常重要的。 这些钩子函数可以在组件的不同阶段执行特定的逻辑操作,从而避免因页面数据加载顺序不及预期而造成的异常。 本文就结合 Vue 2.x 版本简单列一下都有哪些钩子。 ## 一、都有哪几种生命周期钩子 每个 Vue 组件实例在创建时都需要经历一系列的初始化步骤,比如设置好数据侦听,编译模板,挂载实例到 DOM,以及在数据改变时更新 DOM。 在此过程中,它也会运行被称为生命周期钩子的函数,让开发者有机会在特定阶段运行自己的代码。 下面是实例生命周期的图表。 ![](https://cdn.res.knowhub.vip/c/2505/22/633bf2df.png?G1YAAMTydJz4v%2btDuo06fJsoEpoBiSyCSgnr9Z6z9i3y%2fU6lxWe0Pn1%2f%2bEvr0wU1VxqEyqQJwRPIUBp4hcKiUELjGg4%3d) | | | | | | | --- | --- | --- | --- | --- | | **钩子** | **是否可访问 data** | **是否可以访问 DOM** | **常见用途** | **使用建议** | | **beforeCreate** | - | - | 初始化前,极少使用 | - | | **created** | √ | - | 发送请求、初始化数据 | 在此阶段获取数据,避免阻塞渲染 | | **beforeMount** | √ | - | DOM 挂载前准备 | - | | **mounted** | √ | √ | DOM 操作、第三方库初始化 | 在此阶段处理 DOM 或初始化插件(如地图、图表) | | **beforeUpdate** | √ | - | 数据发生变更,DOM 需要更新之前触发 | - | | **updated** | √ | √ | 数据变更后,DOM 更新完毕时触发 | 在此阶段对 DOM 做动态调整(如自动聚焦、布局重算) | | **beforeDestroy** | √ | √ | 清理资源(定时器、事件监听) | 在此阶段中清理占内存比较多的变量,避免泄露 | | **destroyed** | - | - | 执行那些不需要访问组件内部数据或DOM的操作 | 例如,发送日志、通知外部系统组件已经被销毁等 | *参考:https://cn.vuejs.org/guide/essentials/lifecycle* ## 二、简介 ### 2.1 beforeCreate:实例初始化之后立即执行 beforeCreate 是在整个 Vue 实例初始化过程中非常早期的一个阶段被调用。 beforeCreate 是 Vue 生命周期中**调用的第一个钩子**。 **触发时机是在实例初始化之后,数据观测(data observer)和事件/侦听器配置之前被调用。** 在 beforeCreate 钩子中,Vue 实例尚未完成初始化,这意味着**此时还无法访问或操作实例中的 data 属性、计算属性(computed)、方法(methods)**等。 此时 Vue 尚未开始编译模板,因此也**无法访问 $el 或者通过 $refs 访问 DOM 元素**。 由于以上两个原因,beforeCreate 就变得极不常用。 但是存在就有它的用途,例如: 1. 执行不依赖于 Vue 实例状态的操作:例如设置全局事件监听器,虽然这种情况较为少见。 2. 集成第三方库:有时可能需要在 Vue 实例完全初始化之前做一些准备工作,比如与外部脚本或库的交互。 **在多数情况下,更推荐于使用 created 来代替 beforeCreate,因为 created 提供了对数据和方法的访问权限。** ### 2.2 created:完成了数据观测(data observer)、属性和方法的运算之后 created 生命周期钩子是一个非常重要的阶段,它**标志着 Vue 实例已经完成了数据观测(data observer)、属性和方法的运算,但尚未开始模板编译,也未挂载到 DOM 上**。 此阶段的特点: - **可以访问到 data、methods、computed** 等属性。 - 此时组件还没有被挂载到页面上,所以**不能访问或操作 $el 或者通过 $refs 访问 DOM 元素**。 - 因为此阶段可以访问到组件的数据和方法,因此它是**发起异步请求(如 API 请求)来获取数据的好地方**。 - 可以在此阶段**进行一些初始化工作**,比如监听某些事件,或者调用某些方法来初始化组件状态。 created 阶段可以干什么?例如: - **数据初始化**:从服务器获取数据并填充到组件的数据属性中。 - **事件监听**:添加全局或局部的事件监听器。 - **第三方库集成**:如果需要与第三方 JavaScript 库集成,可以在 created 钩子中进行必要的初始化工作。 虽然 created 钩子可以执行许多初始化任务,但它并**不适合执行复杂的计算或大规模的操作,因为这些可能会导致性能问题**。 created 钩子是在**实例创建完成之后立刻调用**,而 mounted 是在**实例挂载到 DOM 之后调用**。如果你需要直接操作 DOM,应该使用 mounted 而不是 created。 **在 Element UI 中,可以在 created 阶段请求数据并绑定给 tableData,用于后续渲染 <el-table>。** 如下一个简单的示例: ```javascript new Vue({ el: '#app', data() { return { message: 'Hello Vue!', posts: [] } }, created() { console.log('created hook called'); // 在这里可以访问到 this.message 和其他 data 属性 console.log(this.message); // 输出 "Hello Vue!" // 发起异步请求加载数据 this.fetchPosts(); }, methods: { fetchPosts() { // 模拟异步请求 setTimeout(() => { this.posts = [ { id: 1, title: 'Vue.js 基础' }, { id: 2, title: '深入理解 Vue 组件' } ]; }, 1000); } } }); ``` ### 2.3 beforeMount:模板已编译完成,但未挂载到页面上 触发时机是实例挂载之前,即**模板编译完成,但尚未将渲染好的 DOM 挂载到页面上时**触发。 DOM 尚未生成。Vue 已经完成了模板编译,但是还没有将虚拟 DOM 渲染并插入到实际的文档中。因此,无法访问或操作真实的 DOM 元素。 数据已经响应式处理。此时组件的数据已经被设置为响应式,这意味着你可以访问 data 和 methods 等属性,但还不能对真实 DOM 做任何修改。 极少直接使用。因为其特殊的位置,beforeMount 并不是特别常用。大多数情况下,**开发者更倾向于在 mounted 钩子里执行需要与 DOM 相关的操作**。 尽管 beforeMount 的应用场景有限,但在某些特定的情况下仍然有用: 1. 准备即将挂载的组件:如果需要在组件挂载前做一些准备工作,比如设置一些状态或预加载资源,可以考虑在此阶段进行。 2. 性能监控:用于记录组件从创建到挂载的时间点,帮助分析应用的性能瓶颈。 ```javascript new Vue({ el: '#app', data() { return { message: 'Hello Vue!' } }, beforeMount() { console.log('beforeMount hook called'); // 注意:这里可以访问 this.message,但是不能访问真实的 DOM 元素 console.log(this.$el); // undefined 或者即将被替换的原始元素 }, mounted() { console.log('mounted hook called'); // 此时可以访问真实的 DOM 元素 console.log(this.$el); // 实际的 DOM 节点 } }); ``` ### 2.4 mounted:编译已完成,且已将渲染好的 DOM 挂载到页面上 当 mounted 钩子被调用时,说明**组件已经被插入到 DOM 中,所有的子组件也都已经挂载完成**。这是整个生命周期中最先能够访问或操作 DOM 的阶段。 可进行的操作: - DOM 操作:由于在这个钩子内可以访问到真实的 DOM 元素,因此非常适合进行那些需要直接操作 DOM 的工作,比如初始化第三方库(如地图、图表等),或者手动设置一些样式。 - 异步数据获取:虽然也可以在 created 钩子中进行数据获取,但由于此时 DOM 尚未准备好,如果你的数据获取逻辑依赖于特定的 DOM 状态,则应考虑在 mounted 中执行。 - 事件监听器的添加:此阶段还可以给非 Vue 管理的 DOM 元素添加事件监听器。 如果在 mounted 钩子中添加了任何全局或第三方的资源引用(如事件监听器或定时器),请确保在组件销毁之前(通常是在 beforeDestroy 或 destroyed 钩子中)进行适当的清理,以防止内存泄漏。 在 Vue 2.0 中,mounted 钩子提供了一个理想的切入点,可以在组件挂载完成后立即执行必要的操作,特别是那些涉及到 DOM 操作和外部库集成的场景。正确利用它可以极大地提高应用的灵活性和功能丰富度。 **在 Element UI 中,可以实现对 <el-select> 下拉框进行手动聚焦,初始化 ECharts 图表等。** ### 2.5 beforeUpdate:已发现 DOM 树的变更点,着手更新前调用此回调 当 Vue 检测到组件中的响应式数据发生了变化,并且这些变化导致了虚拟 DOM(Virtual DOM)的重新渲染时,**Vue 会先进行一次 diff 算法来比较新旧虚拟 DOM 树的不同之处**。在确定了需要更新的实际 DOM 后,Vue 将会执行必要的 DOM 操作以反映数据的变化。就在此**执行 DOM 操作之前,beforeUpdate 钩子会被触发**。这个钩子允许在组件的 DOM 更新前执行自定义逻辑。 可以进行哪些操作: - 访问当前 DOM 状态:如果你想**在更新前 访问或检查当前的 DOM 元素状态**,例如获取某些元素的尺寸、位置等信息,可以在 beforeUpdate 钩子中进行。 - **手动清理或移除事件监听器**:如果你在组件内动态添加了事件监听器或者其他需要手动清理的资源,在这里可以进行相应的清理工作,以避免不必要的内存泄漏或者副作用。 - **性能监控与调试**:可以通过 beforeUpdate 来记录组件更新的频率、查看数据变化情况,有助于性能优化和问题定位。 **在 beforeUpdate 钩子内部,DOM 元素是上一次更新的结果**,即还没有应用新的数据更改到 DOM 上的状态。因此,如果想要根据最新的数据状态来操作 DOM,应该考虑使用 updated 钩子。 **不建议在 beforeUpdate 钩子中改变组件的状态(如修改 data 属性)**,因为这可能会导致无限循环更新的问题,既然已经进入了更新周期,尝试再次触发更新会导致递归调用,影响性能甚至引发错误。 通过 beforeUpdate 生命周期钩子,可以组件的行为更加精细化的控制,特别是在处理复杂的用户交互或需要精细调整的界面更新逻辑时显得尤为重要。 ### 2.6 updated:组件因为响应式数据的变化而更新其 DOM 结构之后被调用 与 beforeUpdate 不同,**updated 则是在执行 DOM 操作完成之后才会触发。** 可以进行 DOM 更新后的操作。某些需要在 DOM 更新后执行某些操作,比如**动画控制、第三方库的初始化或与服务器同步状态**等,就可以在 updated 钩子中进行。 注意:由于数据变化可能导致视图的多次重绘,**在 updated 钩子里要小心不要进行可能会再次触发数据变化的操作**,以免造成无限循环更新。 **在 Element UI 中可以在表格数据更新后,重新调整列宽。** Vue 实现了一个异步更新队列,这意味着当**同时修改多个响应式数据时,Vue 会将它们合并为一次更新**。因此,如果想要确保所有的更新都已经完成并反映到了 DOM 上,可以使用 **this.$nextTick 方法,它会在 DOM 更新循环结束后执行回调函数**。 下面是一个简单的示例: ```javascript ``` ### 2.7 beforeDestroy:在整个 Vue 实例销毁之前调用 这个阶段,Vue 实例仍然完全可用,所有数据绑定、事件监听器以及子组件都还处于完整可用的状态,这意味着还可以执行一些操作,如清理工作、取消网络请求或定时器等。 可以进行的操作: - **资源清理**:此阶段是进行资源管理的最佳位置,比如清除定时器、取消尚未完成的网络请求或者移除手动添加的 DOM 元素事件监听器。 - **状态保存**:如果你的应用有需要在组件销毁前保存的状态(例如:用户输入但未提交的数据),可以在此阶段进行处理。 - **解绑全局事件**:如果在组件中通过 $on 或者直接使用 JavaScript 在全局对象上绑定了事件,应该在此处进行解绑,以避免内存泄漏。 一旦 beforeDestroy 钩子中的代码执行完毕,Vue 将开始卸载组件的过程,包括但不限于解除对父级组件的引用、销毁所有子组件实例以及移除 DOM 元素。 例如在 Element UI 中,可以在此阶段销毁页面时清除全局 loading 效果。 注意:在 Vue 3 中,beforeDestroy 被重命名为 beforeUnmount,以更好地反映其实际意义,即它是在组件卸载之前被调用,而不是真正的“销毁”。 ### 2.8 destoryed:**在 Vue 实例被销毁之后调用** 在这个阶段,Vue 实例的所有指令都被解绑,所有事件监听器也被移除,所有的子实例也都被销毁。这意味着组件已经完成了它的使命,并且从 DOM 中完全移除了。 可以进行的操作: - **清理工作:**比如你可能需要取消一些尚未完成的异步请求,或者移除手动添加的事件监听器(不在 Vue 的事件系统中注册的),以避免内存泄漏。 - **记录或报告:**可以用来记录日志或者发送分析数据,表明某个组件已经被销毁。 - **执行最后的操作:**例如通知其他系统组件已销毁等。 注意:如果组件间有依赖关系,需要确保在销毁时,正确地处理这些依赖,以便它们也能得到适当的清理。 下面是一个简单的示例: ```javascript new Vue({ el: '#example', data: { message: 'Hello Vue!' }, methods: { // 假设这里有一个定时器 startPolling: function() { this.pollingTimer = setInterval(() => { console.log(this.message); }, 1000); } }, mounted: function() { this.startPolling(); }, beforeDestroy() { // 在组件销毁前清除定时器 // 在页面退出前清理定时器的最佳实践就是在 beforeDestroy 中进行,因为这个时候组件还“活着”,可以确保所有的资源都被正确释放 clearInterval(this.pollingTimer); console.log('Before Destroy: Timer cleared.'); }, destroyed() { // 组件已被销毁后的操作 console.log('Destroyed: Component A has been removed.'); // 到了 destroyed 钩子,组件已经不再活跃,如果定时器没有在 beforeDestroy 中被清除,则可能已经导致了不必要的资源占用甚至内存泄漏 } }); ``` 另外,在 Vue 3 中被重命名为 unmounted。 ## 三、另外几个基础概念 - **什么是:数据观测(Data Observer)?** Vue 将 data 函数返回的对象中的所有属性,转换为 getter/setter 形式,以便追踪依赖并进行响应式更新。 - **什么是:属性和方法运算?** Vue 对所有组件的属性(props)、计算属性(computed)以及方法(methods)进行解析,之后就可以在实例中访问了。 - **什么是:事件监听器设置?** 将所有通过 events 或直接在模板上定义的事件监听器设置完成。 - **什么是:模板编译?** Vue 的模板编译过程是将**模板(HTML)**转换为**渲染函数(render function)**的过程,这个过程对于 Vue 组件的渲染至关重要。 编译过程总共有三步: 1. 模板解析(Template Parsing):在这一阶段,Vue 会将页面模板字符串解析成**抽象语法树(AST)**。AST 是一种树形结构,用来表示模板中的每一个元素、属性及其关系。这一步骤使得 Vue 能够理解和处理页面模板代码。 2. 优化(Optimization):生成 AST 后,Vue 对其进行静态分析以优化后续更新检查的效率。**Vue 会标记静态节点和静态根节点。**静态节点是指那些不会随应用状态变化而改变的节点。通过这种优化,Vue 可以跳过这些静态部分,在重新渲染时只关注动态内容的变化,从而提升渲染性能。 3. 代码生成(Code Generation):最后一步是根据 AST 生成渲染函数(render function)。渲染函数是一个 JavaScript 函数,它描述了如何创建虚拟 DOM 来匹配模板。在 Vue 2 中,可以直接编写渲染函数而不是使用模板;而在 Vue 3 中,即使使用的是单文件组件(SFC),最终也会被编译成渲染函数。 渲染函数和虚拟 DOM: 生成的渲染函数会被用来创建虚拟 DOM 树。虚拟 DOM 是一个轻量级的内存数据结构,它模仿真实的 DOM 树结构。当组件的状态发生变化时,Vue 会重新运行渲染函数以生成新的虚拟 DOM 树,并通过差异算法(diffing algorithm)对比新旧两棵树的不同之处,仅对实际发生变更的部分进行必要的真实 DOM 更新。