学习目标:
了解VUE的生命周期,并可以熟练使用VUE2、VUE3常用的生命周期函数。
学习内容:
- 什么是钩子函数
- VUE2和VUE3的钩子函数对比
- 钩子函数使用场景
- 钩子函数应用实例
1.什么是钩子函数
钩子函数一般是指vue自带的描述组件的生命周期的函数,当然还有路由钩子、Vuex 插件钩子 等,此处只讲组件的生命周期函数。这些是在组件生命周期特定阶段自动调用的函数,下面以组合式API为例,组件的各个阶段如下图:
2.VUE2和VUE3的钩子函数对比
Vue 3 的生命周期钩子在功能上与 Vue 2 基本一致,但通过组合式 API 提供了更灵活的使用方式。主要变化包括:
- 命名调整:beforeDestroy → beforeUnmount,destroyed → unmounted。
- 组合式 API:使用 onXxx() 函数替代选项式钩子。
- 新增调试工具:onRenderTracked 和 onRenderTriggered。
- setup() 替代 beforeCreate/created:组件初始化逻辑统一放入 setup()。
下面通过表格进行对比:
VUE2 选项式API | VUE3选项式API | VUE3组合式API | 描述 |
---|---|---|---|
beforeCreate | beforeCreate | setup() | 组件实例初始化之后,数据观测 (data 和 props) 和 event/watcher 事件配置之前。几乎没有可用资源 |
created | created | setup() | 实例已经创建完成之后,数据观测 (data 和 props)、property 和 method 计算、watch/event 事件回调已经配置完成,但此时DOM 未生成 |
beforeMount | beforeMount | onBeforeMount() | 模板编译 / 挂载之前,render 函数首次被调用,template 到 render 函数的编译已完成,虚拟dom已生成,但是真实DOM尚未挂载 |
mounted | mounted | onMounted() | DOM 已挂载,可以获取完整的 DOM 结构、子组件 |
beforeUpdate | beforeUpdate | onBeforeUpdate() | 数据更新后,DOM 重新渲染之前 |
updated | updated | onUpdated() | 数据更新后,DOM 重新渲染完成后 |
beforeDestroy | beforeUnmount | onBeforeUnmount() | 组件卸载(销毁)前 |
destroyed | unmounted | onUnmounted() | 组件卸载(销毁)后 |
activated | activated | onActivated() | 用于管理被 keep-alive 缓存的组件,被缓存的组件不会触发 mounted 和 destroyed 钩子,而是通过 activated 和 deactivated 替代,缓存的组件重新进入视图时触发 |
deactivated | deactivated | onDeactivated() | keep-alive缓存的组件离开视图时触发 |
- | renderTracked | onRenderTracked() | 新增:在组件渲染过程中追踪到响应式依赖时触发(开发环境下生效) |
- | renderTriggered | onRenderTriggered() | 新增:当响应式依赖变更导致组件重新渲染时触发(开发环境下生效) |
errorCaptured | errorCaptured | onErrorCaptured() | 抓取异常信息,当子孙组件抛出错误时触发(可捕获同步错误和 Promise 异步错误) |
3.钩子函数使用场景
- beforeCreate和beforeMount使用场景较少;
- created 一般会处理一些异步操作的逻辑,比如发起异步请求获取页面初始数据。也可以在这里注册全局事件监听器。
- mounted 适合操作 DOM 或初始化依赖 DOM 的插件,此时也可以访问ref引用的元素;但对于大型的组件,一般会再叠加nextTick或者async/await去确保DOM更新完成后再去执行。
export default {
created() {
// 适合发起异步请求(此时数据已初始化,但 DOM 未挂载)
this.fetchData();
},
mounted() {
// 适合操作 DOM 或初始化依赖 DOM 的插件
this.initChart();
this.nextTick(()=>{
// 大型组件的话 等待 DOM 更新完成再操作dom,逻辑写这里
})
},
}
- beforeUpdate 缓存旧 DOM 状态(如滚动位置、动画状态);取消不必要的监听器(如暂停依赖旧数据的实时计算或者监听器)。
- updated 对新生成的DOM 操作;恢复监听器(恢复在 beforeUpdate 中暂停的计算)。
updated() {
// 恢复滚动位置到更新前的状态
this.$el.scrollTop = this.scrollPosition;
// 更新第三方插件(如 Chart.js)
if (this.chart) {
this.chart.update();
}
}
- beforeDestroy / beforeUnmount 取消未完成的请求、清除定时器;移除通过 $on 或 window.addEventListener 绑定的事件监听器;保存临时状态(如在组件销毁前保存用户输入或临时数据)。
beforeDestroy() {
// 清理定时器
clearInterval(this.timer);
// 解绑全局事件监听器
window.removeEventListener('resize', this.handleResize);
// 保存临时数据到本地存储
localStorage.setItem('tempData', JSON.stringify(this.tempData));
}
- destroyed / unmounted 埋点统计(发送组件销毁的通知);释放第三方库占用的资源(如 WebSocket 连接)。
destroyed() {
// 通知全局状态管理(如 Vuex)
this.$store.dispatch('componentDestroyed', this.name);
// 关闭 WebSocket 连接
if (this.socket) {
this.socket.close();
}
}
- renderTracked 可以查看组件渲染时依赖了哪些数据。
renderTracked(event) {
// event 对象包含: target:被追踪的响应式对象; key:被访问的属性名; type:操作类(get、has 等)
console.log('追踪到依赖:', event.target, event.key);
// 示例:检查是否存在意外的依赖
if (event.key === 'expensiveData') {
console.warn('组件渲染依赖了高开销数据');
}
}
- renderTriggered 可以定位导致组件频繁更新的原因。
renderTriggered(event) {
// event 对象包含: target:触发变更的响应式对象 ;key:变更的属性名; type:变更类(set、add 等); newValue/oldValue:新旧值
console.log('触发重渲染:', event.key, event.newValue);
// 检查是否因非必要数据变更导致重渲染
if (event.key === 'nonCriticalFlag') {
console.warn('非关键数据变更触发了组件重渲染');
}
}
- errorCaptured 统一收集和上报组件错误进行全局错误处理(主意:组件自身的 beforeCreate/created 钩子中的错误和全局错误,如 window.onerror)。
errorCaptured(err, vm, info) {
// err:错误对象; vm:抛出错误的组件实例; info:错误来源信息(如 "render"、"v-on handler")
// 1. 记录错误日志(可发送到服务端)
console.error('捕获到错误:', err, info);
this.$store.dispatch('logError', { err, info });
// 2. 提供降级显示
this.showErrorFallback = true;
// 3. 阻止错误继续向上传播(可选)
// return false;
}
4.钩子函数应用实例
3中已经有不少,后续添加上实际项目中的代码(不出意外应该是优化相关)。
总结:
钩子函数的使用在VUE中很常见,学会合适地在哪些阶段做哪些事情,可以使我们的代码逻辑清晰而规整。而如果用不好则也会出现对应的bug,如在updated 中修改数据可能导致无限循环更新、 unmounted 中修改数据发现无效,因为组件已销毁等等,在了解了每个阶段的意义后再去使用则出现这种问题的概率将会大大减少。