Vue3 生命周期钩子详解
Vue3 生命周期钩子详解
概述
生命周期钩子让你在组件运行的不同阶段注入自定义逻辑。Vue3 的组合式 API 提供了一套与选项式 API 一一对应的生命周期钩子函数,每个钩子都在组件从创建到销毁的特定时机被调用。理解每个钩子的执行时机和适用场景,是编写健壮 Vue3 组件的必备技能。
组件生命周期全流程
Vue3 组件从创建到销毁经历以下阶段:初始化 → 挂载 → 更新 → 卸载。每个阶段都有对应的钩子函数,组合式 API 的钩子函数需在 setup 或 <script setup> 中调用。
基础语法
import {
onMounted, // 挂载后:DOM 已插入,可访问 $el
onUpdated, // 更新后:VDOM 重新渲染并应用到真实 DOM
onUnmounted, // 卸载前:组件即将销毁,清理定时器、事件监听
onBeforeMount, // 挂载前:render 函数首次被调用
onBeforeUpdate,// 更新前:数据变化,DOM 更新前
onBeforeUnmount,// 卸载前:组件实例仍完全正常
onErrorCaptured,// 捕获子孙组件抛出的错误
onRenderTracked,// VDOM render 时自动追踪响应式依赖(调试用)
onRenderTriggered,// VDOM re-render 时手动追踪触发源(调试用)
onActivated, // 配合 keep-alive:被激活时
onDeactivated, // 配合 keep-alive:被停用时
} from 'vue'
完整代码示例
<script setup>
import {
ref, reactive, onMounted, onUpdated, onUnmounted,
onBeforeMount, onBeforeUpdate, onBeforeUnmount,
onErrorCaptured, onRenderTracked, onRenderTriggered,
onActivated, onDeactivated, nextTick
} from 'vue'
console.log('[阶段0] setup 执行(组件初始化)')
// ========== 1. 挂载阶段 ==========
onBeforeMount(() => {
// 真实 DOM 还不存在,不能访问 this.$el
console.log('[阶段1] onBeforeMount - 即将挂载,el 是否存在:', false)
})
onMounted(() => {
// 组件 DOM 已插入,可安全访问 this.$el、document.querySelector
console.log('[阶段1] onMounted - DOM 已挂载')
console.log(' mounted 可访问 el:', document.querySelector('.lifecycle-demo'))
// 初始化第三方图表库(如 ECharts)
initChart()
// 绑定全局事件
window.addEventListener('resize', handleResize)
})
// ========== 2. 更新阶段 ==========
const message = ref('初始消息')
function updateMessage() {
message.value = '消息已更新 - ' + new Date().toLocaleTimeString()
}
onBeforeUpdate(() => {
// DOM 还未更新,可在此做旧 DOM 快照
const el = document.querySelector('.message-box')
console.log('[阶段2] onBeforeUpdate - DOM 将更新,current text:', el?.textContent)
})
onUpdated(() => {
// DOM 已更新完成,常用于操作更新后的 DOM(如聚焦光标)
console.log('[阶段2] onUpdated - DOM 已更新')
nextTick(() => {
console.log('nextTick: DOM 完全稳定')
})
})
// ========== 3. 卸载阶段 ==========
let timer = null
onBeforeUnmount(() => {
// 组件仍完全可用,可取消异步请求
console.log('[阶段3] onBeforeUnmount - 即将卸载,清理前准备')
if (timer) clearInterval(timer)
})
onUnmounted(() => {
// 完全清理:事件监听、定时器、订阅
console.log('[阶段3] onUnmounted - 组件已卸载')
window.removeEventListener('resize', handleResize)
timer = null
})
// ========== 4. 错误捕获 ==========
onErrorCaptured((err, instance, info) => {
console.error('[错误捕获] 捕获到错误:', err.message)
console.error('[错误捕获] 错误来源组件:', instance)
console.error('[错误捕获] 错误信息:', info)
return false // 返回 false 阻止错误继续向上传播
})
// ========== 5. 调试追踪(开发模式) ==========
// 每次 render 追踪所有响应式依赖(类似松散模式的依赖日志)
onRenderTracked(({ key, target, type }) => {
console.log(`[追踪] 访问了 ${type} ${String(key)} on`, target)
})
// 每次 render 由数据变化触发时,追踪是哪个数据变化导致的
onRenderTriggered(({ key, target, type }) => {
console.log(`[触发] ${type} ${String(key)} 变化,触发了 re-render`)
})
// ========== 辅助函数 ==========
let chartInstance = null
function initChart() {
// 模拟初始化 ECharts
chartInstance = { render: () => console.log('图表渲染') }
console.log('图表库初始化完成')
}
function handleResize() {
if (chartInstance) chartInstance.render()
console.log('窗口大小变化,重新布局')
}
// ========== keep-alive 钩子(配合 <keep-alive> 使用)==========
onActivated(() => {
console.log('[keep-alive] 组件被激活(从缓存恢复)')
})
onDeactivated(() => {
console.log('[keep-alive] 组件被停用(进入缓存)')
})
</script>
<template>
<div class="lifecycle-demo">
<h2>Vue3 生命周期演示</h2>
<div class="message-box">{{ message }}</div>
<button @click="updateMessage">更新消息</button>
<p style="margin-top: 1rem; font-size: 0.85rem; color: #666;">
打开浏览器控制台,观察各生命周期钩子的执行顺序。
</p>
<p style="margin-top: 0.5rem; font-size: 0.85rem; color: #666;">
注意:在组件树中触发一个故意报错,查看 onErrorCaptured 的捕获效果。
</p>
</div>
</template>
<style scoped>
.lifecycle-demo { padding: 1rem; border: 1px solid #ddd; border-radius: 8px; }
</style>
运行效果说明
控制台按以下顺序输出日志:setup → onBeforeMount → onMounted(初始化)→ onBeforeUpdate → onUpdated(更新时)。点击"更新消息"按钮触发更新流程。组件卸载时 onBeforeUnmount 和 onUnmounted 依次执行,定时器和事件监听被正确清理。如果子孙组件抛出错误,onErrorCaptured 捕获并打印详细信息。
常见问题
Q1:onMounted 和 onBeforeMount 的核心区别是什么?
onBeforeMount 在组件 render 函数执行之后、真实 DOM 插入之前调用,此时 document.querySelector 只能找到模板中使用了 ref/reactive 注册过的 DOM 占位符,无法安全操作真实 DOM。onMounted 在 DOM 真正插入完成后调用,此时可以安全执行 document.querySelector、初始化第三方库(如 ECharts、Swiper)、执行需要真实 DOM 尺寸的逻辑。
Q2:onUpdated 中如何避免无限更新循环?
onUpdated 中修改响应式数据会触发新一轮更新,容易形成死循环。解决方案:① 确保在 onUpdated 中只做纯 DOM 操作(如聚焦、滚动),不修改响应式数据;② 如必须修改,应使用 nextTick 包装,并通过条件判断防止重复执行;③ 检查数据修改是否有条件限制,避免每次更新都触发数据变化。
Q3:onErrorCaptured 和全局错误处理有什么区别?
onErrorCaptured 是组件级错误捕获,只能捕获当前组件及其子孙组件的错误,捕获后可以决定是否向上传播(返回 false 阻止传播)。app.config.errorHandler(Vue3 全局错误处理器)捕获所有未被处理的错误,包括 onErrorCaptured 返回 false 之后的错误。建议两者配合使用:局部用 onErrorCaptured 做组件特定的错误恢复,全局处理器负责统一日志上报。
延伸阅读
- <a href="#">Vue3 组合式 API 完全指南</a> —— 组合式 API 完整入门
- <a href="#">Vue3 依赖注入 provide/inject 详解</a> —— 深层组件间通信的响应式方案
- <a href="#">Vue3 Teleport 组件深度解析</a> —— 任意位置渲染组件内容的技术实现