什么是 Vue 钩子函数?——别被名字吓住
很多开发者初识 Vue 时,听到“钩子函数”就头大。其实这玩意儿没那么玄乎——它就是 Vue 在组件从创建到销毁整个生命周期中,为你预留的若干个“时间戳点”,你可以在这些点上插入自己的逻辑代码。
你可以把组件生命周期想象成一场电影:从剧本(script)准备、开机(create)、拍摄(mounted)、高潮(updated)、到杀青(destroy),每个关键节点,导演都会喊“卡”,这时候你就可以调整灯光、换道具、甚至临时改剧本——这些“卡点”,就是 Vue 的钩子函数。
核心钩子函数包括:beforeCreate、created、beforeMount、mounted、beforeUpdate、updated、beforeDestroy、destroyed,以及 Vue 3 引入的 setup 和 onUnmounted 等 Composition API 钩子。
生命周期全景图:从“出生”到“谢幕”
我们先看 Vue 2 的完整生命周期流程图(Vue 3 逻辑类似,但 API 有调整):
beforeCreate
实例初始化后,data、methods 等尚未初始化,无法访问任何实例成员。此时连 this.$data 都是 undefined。
created
data 和 methods 已初始化,可访问数据与方法,但 DOM 尚未生成。适合做数据预加载、事件绑定等。
beforeMount
模板编译完成,虚拟 DOM 已生成,但尚未挂载到真实 DOM。此时 this.$el 指向的是占位元素(如 <div id="app"></div>)。
mounted
组件已挂载到 DOM,可操作真实 DOM 元素。这是最常用的钩子,适合发起 AJAX 请求、初始化第三方插件(如 ECharts)等。
beforeUpdate
数据更新时触发,DOM 尚未重新渲染。此时可访问更新前的 DOM 状态,适合做性能对比(如记录 DOM 节点数量变化)。
updated
DOM 已更新完毕。注意:避免在此钩子中直接修改数据,否则可能引发无限循环更新!
beforeDestroy
实例即将销毁,但尚未解绑事件、移除监听器。适合做清理工作:清除定时器、取消网络请求、解绑全局事件等。
destroyed
实例已完全销毁,所有绑定解除、子实例销毁。此时访问 this 仍可获取实例,但已无实际意义。
注意:Vue 3 中,Composition API 使用 onMounted、onUnmounted 等函数替代选项式钩子。例如:
响应式原理:Vue 如何知道数据变了?
很多人以为 Vue 的响应式是“魔法”,其实它是基于 JavaScript 原生能力实现的精密系统。核心有三要素:
- 依赖收集:当组件渲染时,Vue 会遍历模板中的数据,同时将当前活跃的
Watcher(观察者)记录到对应数据的依赖列表中。 - 数据劫持:通过
Object.defineProperty(Vue 2)或Proxy(Vue 3)拦截数据的读取(get)和设置(set)操作。 - 派发更新:当数据被修改时,触发
set,通知所有依赖该数据的Watcher执行更新。
我们用一个简化版示例还原 Vue 2 的响应式逻辑:
注意:Vue 并不是“监听所有属性变化”,而是只在组件首次渲染时触发 get,此时才进行依赖收集。因此,动态新增的属性(如 this.obj.newKey = 'value')无法触发更新——这就是 Vue.set 的由来。
watch vs computed:不是“监听”,而是“计算”
这是新手最容易混淆的点!我们用表格对比核心差异:
computed(计算属性)
- 基于依赖缓存:依赖不变则不重新计算
- 同步执行,无异步支持
- 必须有返回值
- 适合“派生数据”场景
watch(侦听器)
- 无缓存:每次触发都执行
- 支持异步操作
- 可执行副作用(如 API 调用)
- 适合“观察数据变化并执行操作”
再看一个经典案例:当用户输入时,实时计算搜索结果数量,并同步更新页码到 URL。
特别注意:当 watch 中访问了其他 computed 属性时,会间接触发依赖链。例如:
data 的“陷阱”:你可能一直用错了
很多教程只说“data 必须是函数”,却不解释为什么。我们来深挖原因:
当组件被复用时(如 <my-list> 出现多次),如果 data 是对象,所有实例会共享同一份数据——修改一个,全部跟着变!而函数每次返回新对象,确保实例隔离。
new Vue(根实例)的 data 可以是对象,因为根实例不会复用;但组件的 data 必须是函数!
另一个陷阱是:为什么修改数组方法(如 push)能触发更新,但直接赋值索引(this.list[0] = 'new')不行?
这是 JavaScript 本身的限制!Object.defineProperty 无法拦截数组索引的读写。Vue 通过重写 7 个数组变异方法(push、pop、shift、unshift、splice、sort、reverse)来触发更新。因此:
- ✅
this.list.push('new')→ 触发更新 - ❌
this.list[0] = 'new'→ 不触发更新 - ✅
this.$set(this.list, 0, 'new')→ 手动触发 - ✅
this.list = [...this.list.slice(0,0), 'new', ...this.list.slice(1)]→ 替换整个数组
增量渲染:Vue 如何做到“只改需要改的”?
很多开发者以为 Vue 是“整个 DOM 重绘”,其实它是基于虚拟 DOM(VNode)的差异比对(Diff)算法,实现“精准局部更新”。核心流程如下:
模板编译
将 <div>{{msg}}</div> 编译成渲染函数,生成 VNode 树。例如:
虚拟 DOM 创建
首次渲染时,VNode 被转换为真实 DOM;后续更新时,新旧 VNode 进行 patch 对比。
Diff 算法
采用“同层比较”策略:只比较同一层级节点,不跨层移动。核心优化点:
- 利用
key标识节点唯一性(避免无意识的节点复用) - 优先比较静态节点(标记为
static的节点不参与 Diff) - 头尾指针优化:对列表更新(如排序)大幅减少比较次数
再看一个高效更新的示例:
这里 key="item.id" 的作用至关重要——若省略,Vue 会默认使用“就地复用”策略,导致列表顺序错乱时仍复用旧 DOM,引发显示异常。
实战案例:从“能用”到“懂原理”
案例1:防抖搜索框的正确实现方式
很多开发者这样写:
问题:当 watch 被销毁时,防抖定时器并未清理,导致内存泄漏!
正确姿势:在 created 中创建防抖函数,并在 beforeDestroy 中手动清除:
案例2:动态表单的“状态快照”设计
场景:用户编辑表单时,需支持“撤销上一步”功能。传统做法是用数组存历史记录,但内存占用大。
基于 beforeUpdate 钩子,可实现“精准快照”:
注意:这里用 JSON.parse/stringify 是深拷贝,适用于纯数据对象。若含函数、DOM 节点等,需用 structuredClone(现代浏览器)或 lodash.cloneDeep。
网友最关心的 5 个问题
Q1:为什么 created 中发起 AJAX 比 mounted 更好?
A:因为 created 阶段已完成数据初始化,可提前开始网络请求;而 mounted 需等待 DOM 挂载,可能造成白屏延迟。尤其在 SSR(服务端渲染)场景,mounted 根本不会执行!
Q2:computed 缓存真的能提升性能吗?
A:对复杂计算(如遍历大数组)有效,但对简单操作(如 a + b)反而因缓存检查增加开销。建议:只有当计算成本 > 缓存检查成本时才用 computed。
Q3:nextTick 和 setTimeout 有什么区别?
A:nextTick 基于微任务(Promise.then 或 MutationObserver),在 DOM 更新后立即执行;而 setTimeout 是宏任务,延迟不可控。性能上 nextTick 更优。
Q4:如何避免 updated 中的无限循环?
A:在修改数据前加条件判断。例如:if (this.count < 10) this.count++,或使用 nextTick 延迟修改。
Q5:Vue 3 的 setup 和 Vue 2 的 beforeCreate/created 有何关联?
A:setup 在 beforeCreate 之前执行,且 this 未绑定。因此,Vue 3 中所有逻辑都应在 setup 中定义,选项式 API(如 data)会被转换为 setup 中的返回值。
总结:Vue 钩子函数的“三重境界”
第一重:知道有哪些钩子,能在文档中查到。
第二重:理解每个钩子的执行时机,能在恰当位置插入逻辑(如 mounted 操作 DOM)。
第三重:从响应式系统视角,理解钩子与依赖追踪、虚拟 DOM 的协同关系——
beforeCreate:依赖收集尚未开始,无法访问thiscreated:依赖收集已初始化,但尚未创建 VNodemounted:首次 VNode 渲染完成,DOM 已挂载beforeUpdate:数据变化 → 重新生成 VNode → Diff 前updated:Diff 结束,DOM 已更新
最终,Vue 钩子函数的本质不是“生命周期事件”,而是响应式系统的“状态同步点”。当你能预判数据变化如何触发 DOM 更新,你就真正掌握了 Vue 的核心原理。
延伸阅读:与 vue钩子函数原理-Vue 钩子函数原理相关的周边信息
网友们还关心:
Vue 3 响应式系统升级
Proxy 替代 Object.defineProperty,支持动态属性监听;依赖追踪更精细,内存占用更低。
SSR 中的钩子差异
服务端只执行 beforeCreate 和 created,客户端再执行 mounted,避免 DOM 操作报错。
组合式 API 实践
用 onBeforeMount 等函数替代选项式,逻辑更易复用,尤其适合大型项目拆分。