05 Vue 3 组件生命周期与 DOM 操作
Vue 3 组件生命周期与 DOM 操作一、什么是组件生命周期Vue 组件不是一直存在的。它会经历创建组件实例 ↓ 执行 setup 逻辑 ↓ 生成并挂载 DOM ↓ 响应状态变化并更新 ↓ 从页面中卸载生命周期钩子允许我们在这些阶段执行代码。在组合式 API 中常用钩子包括onBeforeMount();onMounted();onBeforeUpdate();onUpdated();onBeforeUnmount();onUnmounted();快速入门最重要的是onMounted()nextTick()onBeforeUnmount()。二、setup阶段能做什么script setup中的顶层代码会在组件实例创建时执行constloadingref(false);constdatarefDataItem[]([]);这个阶段适合声明响应式状态定义计算属性创建监听器定义函数准备普通配置。但此时模板 DOM 通常还没有挂载完成。三、onMounted()页面已经挂载import{onMounted}fromvue;onMounted((){console.log(组件已经显示到页面);});适合执行首次接口请求读取模板 DOM初始化 Three.js、ECharts 或播放器添加窗口事件启动定时器创建 ResizeObserver。首次加载数据onMounted((){voidloadData();});使用void表示这里主动忽略 Promise 返回值错误应在loadData()内部处理。四、模板 DOM 引用模板div refcontainerRef classchart-container/div脚本constcontainerRefrefHTMLElement();挂载后访问onMounted((){if(!containerRef.value){return;}console.log(containerRef.value.clientWidth);});不要在顶层代码中假设 DOM 已经存在console.log(containerRef.value);// 很可能是 undefined五、nextTick()等待 DOM 更新完成Vue 对状态变化产生的 DOM 更新会进行批处理。items.value.push(newItem);console.log(listRef.value?.clientHeight);此时读取到的高度可能仍是更新前的值。正确方式import{nextTick}fromvue;items.value.push(newItem);awaitnextTick();console.log(listRef.value?.clientHeight);什么时候使用nextTick修改状态后立即测量 DOM动态显示容器后初始化第三方库等待条件渲染产生真实元素更新页面模式后重新计算视口大小。不要滥用普通模板更新不需要手动调用nextTick()。只有后续代码依赖“最新 DOM 已经生成”时才需要。六、初始化第三方库以一个简化画布为例letrenderer:SimpleRenderer|nullnull;onMounted(async(){awaitnextTick();if(!containerRef.value){return;}renderernewSimpleRenderer(containerRef.value);renderer.render();});顺序是组件挂载 ↓ 等待模板 DOM 完成 ↓ 获得容器 ↓ 创建第三方实例七、更新阶段生命周期onBeforeUpdate((){console.log(DOM 更新之前);});onUpdated((){console.log(DOM 更新之后);});这些钩子可以用于调试或特殊 DOM 同步但不应作为普通数据更新的主要手段。为什么不建议在onUpdated中随意修改状态onUpdated((){count.value1;});状态变化会引起更新更新后再次修改状态可能形成循环。一般情况下派生值使用computed特定状态变化使用watchDOM 更新后的特殊操作才使用onUpdated。八、onBeforeUnmount()组件即将离开onBeforeUnmount((){console.log(准备卸载组件);});大型页面中必须清理setIntervalsetTimeoutrequestAnimationFramewindow和 DOM 事件WebSocketResizeObserver第三方库实例WebGL、图表和媒体资源。九、定时器的完整管理consttimerrefReturnTypetypeofsetInterval|null(null);functionstartTimer(){stopTimer();timer.valuesetInterval((){voidloadData();},1000);}functionstopTimer(){if(!timer.value){return;}clearInterval(timer.value);timer.valuenull;}onMounted((){startTimer();});onBeforeUnmount((){stopTimer();});注意startTimer()先调用stopTimer()这样可以避免重复启动多个定时器。十、窗口事件的完整管理错误示例onMounted((){window.addEventListener(resize,(){resizeView();});});匿名函数难以在卸载时移除。推荐functionhandleResize(){resizeView();}onMounted((){window.addEventListener(resize,handleResize);});onBeforeUnmount((){window.removeEventListener(resize,handleResize);});添加和移除必须使用同一个函数引用。十一、动画循环的清理letanimationId:number|nullnull;functionanimate(){animationIdrequestAnimationFrame(animate);renderFrame();}functionstopAnimation(){if(animationId!null){cancelAnimationFrame(animationId);animationIdnull;}}onMounted((){animate();});onBeforeUnmount((){stopAnimation();});如果不取消页面离开后动画仍可能继续运行。十二、第三方实例的销毁letchart:ChartInstance|nullnull;onMounted((){chartcreateChart(containerRef.value!);});onBeforeUnmount((){chart?.dispose();chartnull;});不同库的销毁方法不同常见名称包括dispose() destroy() close() disconnect() remove()使用第三方库时需要查看其资源释放接口。十三、页面启动和退出的完整主链一个复杂页面可以按下面组织onMounted((){heatId.valueString(route.query.heat_id||);if(!heatId.value){router.push(/heat);return;}window.addEventListener(resize,handleResize);startClock();nextTick((){initRenderer();startAnimation();});voidloadData();});onBeforeUnmount((){stopPlayback();stopPolling();stopClock();window.removeEventListener(resize,handleResize);disposeRenderer();});这条主线说明读取页面参数 ↓ 检查是否可以进入 ↓ 添加长期任务 ↓ DOM 完成后初始化视图 ↓ 加载业务数据 ↓ 离开时按相反顺序清理十四、onUnmounted()与onBeforeUnmount()onBeforeUnmount()组件仍然存在适合主动停止任务和释放资源onUnmounted()组件已经卸载适合执行卸载后的通知或确认。大多数资源清理放在onBeforeUnmount()即可。十五、组件缓存与KeepAlive当组件被KeepAlive缓存时它可能不会真正卸载而是进入停用状态。此时还需要了解onActivated();onDeactivated();快速入门阶段只需知道如果页面使用了缓存单纯依赖onBeforeUnmount()可能不足需要根据项目路由布局检查是否使用KeepAlive。十六、常见误区1. 在组件挂载前操作 DOM容器尚不存在初始化失败。2. 创建定时器却不清理重复进入页面后会产生多个轮询任务。3. 添加匿名事件函数离开页面时无法正确移除。4. 销毁 Vue 组件却不销毁第三方实例可能造成内存、GPU 或事件泄漏。5. 所有初始化都塞进onMountedonMounted应负责组织流程复杂逻辑应拆成loadData()、initScene()、startPolling()等函数。十七、本章小结生命周期提供了页面任务的时间边界setup 声明状态和逻辑 onMounted 启动页面任务 nextTick 等待最新 DOM onBeforeUnmount 停止任务并释放资源下一章将学习组件化理解大型页面如何拆成多个职责清晰的.vue组件。