生命周期与模板引用深入理解 Vue3 组件从创建到销毁的完整生命周期流程掌握模板引用与组件引用的新用法。一、前言生命周期是 Vue 组件的核心概念之一它描述了组件从创建、挂载、更新到卸载的完整过程。Vue3 在保留 Vue2 生命周期思想的基础上引入了 Composition API使得生命周期钩子的使用方式发生了显著变化。本文将系统讲解 Vue3 的生命周期钩子、与 Vue2 的对比、模板引用的新用法以及组件挂载的完整流程。二、Vue3 生命周期钩子概览2.1 生命周期钩子列表Vue3 提供了以下生命周期钩子需要在setup函数中显式导入使用生命周期钩子触发时机Vue2 对应钩子onBeforeMount组件挂载到 DOM 之前beforeMountonMounted组件挂载到 DOM 之后mountedonBeforeUpdate组件更新之前beforeUpdateonUpdated组件更新之后updatedonBeforeUnmount组件卸载之前beforeDestroyonUnmounted组件卸载之后destroyedonErrorCaptured捕获后代组件错误时errorCapturedonRenderTracked调试追踪响应式依赖—onRenderTriggered调试触发重新渲染时—onActivated被keep-alive缓存的组件激活时activatedonDeactivated被keep-alive缓存的组件停用时deactivatedonServerPrefetch组件在服务器端渲染前serverPrefetch2.2 基本使用示例script setup import { ref, onBeforeMount, onMounted, onBeforeUpdate, onUpdated, onBeforeUnmount, onUnmounted } from vue const count ref(0) // 挂载前DOM 尚未创建此时无法访问模板引用 onBeforeMount(() { console.log(onBeforeMount: 组件即将挂载) }) // 挂载后可以安全地访问 DOM 和模板引用 onMounted(() { console.log(onMounted: 组件已挂载) }) // 更新前可以获取更新前的 DOM 状态 onBeforeUpdate(() { console.log(onBeforeUpdate: 组件即将更新) }) // 更新后DOM 已更新完成 onUpdated(() { console.log(onUpdated: 组件已更新) }) // 卸载前组件仍完全可用 onBeforeUnmount(() { console.log(onBeforeUnmount: 组件即将卸载) }) // 卸载后清理副作用定时器、事件监听等 onUnmounted(() { console.log(onUnmounted: 组件已卸载) }) /script template div p计数: {{ count }}/p button clickcount增加/button /div /template三、生命周期完整流程3.1 生命周期流程图否是否是创建组件实例初始化响应式数据编译模板onBeforeMount挂载前创建并插入 DOMonMounted挂载完成数据变化?组件卸载?onBeforeUpdate更新前重新渲染 DOMonUpdated更新完成onBeforeUnmount卸载前移除 DOMonUnmounted卸载完成垃圾回收3.2 组件挂载流程详解Vue3 的组件挂载流程可以分为以下几个阶段创建阶段调用setup()函数初始化响应式数据、计算属性和方法编译阶段将模板编译为渲染函数开发环境下由 Vue 编译器完成挂载前阶段执行onBeforeMount此时模板已编译但尚未插入 DOM挂载阶段执行渲染函数创建虚拟 DOM 并 diff生成真实 DOM 插入页面挂载完成阶段执行onMounted此时可以安全访问 DOM 元素script setup import { ref, onBeforeMount, onMounted } from vue const message ref(Hello Vue3) const containerRef ref(null) // 挂载前template 已编译但 DOM 还未生成 onBeforeMount(() { // 此时 containerRef.value 为 null console.log(before mount:, containerRef.value) // null }) // 挂载后DOM 已生成可以安全操作 onMounted(() { // containerRef.value 指向真实 DOM 元素 console.log(mounted:, containerRef.value) // divHello Vue3/div containerRef.value.style.color blue }) /script template div refcontainerRef{{ message }}/div /template四、Vue2 vs Vue3 生命周期对比4.1 选项式 API vs 组合式 API特性Vue2 Options APIVue3 Composition API定义方式在options对象中声明在setup()中导入调用代码组织按选项类型分散按逻辑功能聚合this指向指向组件实例无this通过闭包访问复用逻辑Mixins命名冲突、来源不明Composables清晰、灵活类型推断较差优秀的 TypeScript 支持4.2 代码对比示例Vue2 Options APIexportdefault{data(){return{count:0,timer:null}},mounted(){this.timersetInterval((){this.count},1000)},beforeDestroy(){clearInterval(this.timer)}}Vue3 Composition APIscript setup import { ref, onMounted, onUnmounted } from vue const count ref(0) let timer null onMounted(() { timer setInterval(() { count.value }, 1000) }) onUnmounted(() { clearInterval(timer) }) /script五、模板引用5.1 基本用法模板引用用于直接访问 DOM 元素或组件实例。在 Vue3 中配合ref函数使用script setup import { ref, onMounted } from vue // 声明模板引用 const inputRef ref(null) const divRef ref(null) onMounted(() { // 挂载后自动填充值并聚焦 inputRef.value.value 自动填充 inputRef.value.focus() // 操作 DOM 样式 divRef.value.style.backgroundColor #f0f0f0 }) /script template div !-- 绑定模板引用 -- input refinputRef typetext placeholder请输入 / div refdivRef这是一个 div 元素/div /div /template5.2 v-for 中的模板引用Vue3 中v-for中的模板引用需要绑定到函数script setup import { ref, onMounted } from vue const itemRefs ref([]) const list ref([苹果, 香蕉, 橙子]) onMounted(() { // itemRefs.value 是一个 DOM 元素数组 itemRefs.value.forEach((el, index) { console.log(第 ${index} 项:, el.textContent) }) }) /script template ul li v-for(item, index) in list :keyindex :ref(el) { if (el) itemRefs[index] el } {{ item }} /li /ul /template5.3 组件引用通过模板引用可以访问子组件的属性和方法!-- ChildComponent.vue -- script setup import { ref } from vue const count ref(0) const increment () { count.value } const reset () { count.value 0 } // 显式暴露给父组件 defineExpose({ count, increment, reset }) /script template div子组件计数: {{ count }}/div /template!-- ParentComponent.vue -- script setup import { ref, onMounted } from vue import ChildComponent from ./ChildComponent.vue const childRef ref(null) const handleAdd () { // 调用子组件暴露的方法 childRef.value.increment() } const handleReset () { childRef.value.reset() console.log(当前计数:, childRef.value.count) } /script template div ChildComponent refchildRef / button clickhandleAdd子组件 1/button button clickhandleReset子组件重置/button /div /template5.4 $parent 与 $children 的变化Vue3 中不再推荐使用$parent和$children特性Vue2Vue3$parent直接访问父组件实例仍可用但不推荐破坏封装$children直接访问子组件数组已移除推荐方案—props/emits或provide/injectscript setup // Vue3 中应优先使用 props 和 emits 进行父子通信 import { defineProps, defineEmits } from vue const props defineProps({ message: String }) const emit defineEmits([update]) const handleClick () { emit(update, 新消息) } /script六、setup 中使用生命周期的注意事项6.1 执行顺序在setup中生命周期钩子的注册顺序就是执行顺序script setup import { onMounted } from vue // 多个 onMounted 会按注册顺序执行 onMounted(() { console.log(第一个 onMounted) }) onMounted(() { console.log(第二个 onMounted) }) /script6.2 异步 setupsetup可以是异步函数但需要注意script setup import { onMounted } from vue // 异步 setup const data await fetchData() // 顶层 await // 生命周期钩子仍然有效 onMounted(() { console.log(挂载完成数据:, data) }) /script6.3 条件注册生命周期钩子可以在条件语句中注册script setup import { ref, onMounted } from vue const needTimer ref(true) if (needTimer.value) { onMounted(() { // 只在 needTimer 为 true 时注册 console.log(定时器模式已启动) }) } /script七、调试钩子onRenderTracked 与 onRenderTriggered这两个钩子仅用于调试帮助追踪响应式依赖script setup import { ref, onRenderTracked, onRenderTriggered } from vue const count ref(0) const name ref(Vue3) // 追踪响应式依赖的收集 onRenderTracked((event) { console.log(追踪到依赖:, event) // event.target: 被追踪的响应式对象 // event.type: 追踪类型 (get / has / iterate) // event.key: 被访问的属性键 }) // 追踪重新渲染的触发 onRenderTriggered((event) { console.log(触发重新渲染:, event) // event.target: 触发更新的响应式对象 // event.type: 触发类型 (set / add / delete / clear) // event.key: 被修改的属性键 }) /script template div p{{ name }} 计数: {{ count }}/p button clickcount增加/button button clickname Vue3 Pro改名/button /div /template八、常见问题Q1: 为什么 onMounted 中访问 ref 有时为 null通常是因为组件尚未挂载完成或者使用了v-if条件渲染。确保在onMounted中访问或使用nextTickimport{ref,onMounted,nextTick}fromvueconstelRefref(null)onMounted(async(){awaitnextTick()// 确保 DOM 已更新console.log(elRef.value)})Q2: Vue3 中 destroyed 钩子去哪了Vue3 将beforeDestroy重命名为onBeforeUnmountdestroyed重命名为onUnmounted命名更准确反映实际行为卸载而非销毁。Q3: 如何在组合式函数中注册生命周期钩子组合式函数中可以直接使用生命周期钩子它们会与调用组件的生命周期同步// useAutoIncrement.jsimport{ref,onMounted,onUnmounted}fromvueexportfunctionuseAutoIncrement(initial0,interval1000){constcountref(initial)lettimernullonMounted((){timersetInterval((){count.value},interval)})onUnmounted((){clearInterval(timer)})return{count}}Q4: 父子组件生命周期执行顺序是什么挂载阶段父onBeforeMount→ 子onBeforeMount→ 子onMounted→ 父onMounted卸载阶段父onBeforeUnmount→ 子onBeforeUnmount→ 子onUnmounted→ 父onUnmounted九、总结Vue3 的生命周期系统保留了 Vue2 的核心概念同时通过 Composition API 提供了更灵活的使用方式钩子命名更语义化destroyed→unmounted更准确表达组件状态显式导入所有钩子需要从vue导入Tree-shaking 更友好逻辑聚合相关逻辑可以放在一起不再分散在不同选项中模板引用更简洁配合ref函数使用类型推断更友好组件引用需显式暴露通过defineExpose控制暴露接口封装性更好十、思考题/练习代码练习编写一个自定义组合式函数useCountdown实现倒计时功能在组件卸载时自动清理定时器。对比分析将一段 Vue2 的 Options API 代码包含 data、mounted、beforeDestroy改写为 Vue3 的 Composition API 版本。生命周期顺序画出包含三层嵌套组件祖父 → 父 → 子的挂载和卸载生命周期执行顺序图。实践挑战实现一个useIntersectionObserver组合式函数使用模板引用监听元素是否进入视口。