Vue生命周期钩子:从创建到销毁,每一步都明明白白
一、什么是生命周期你在页面上看到的每一个Vue组件都不是凭空出现的。它有自己的“一生”先被创建出来准备好数据然后挂载到页面上用户能看到数据变化时更新重新渲染最后从页面移除时销毁释放资源整个过程就叫“生命周期”。Vue在每个关键节点都会自动调用一个特定的函数告诉你“到这一步了你要不要做点什么”。这些函数就叫生命周期钩子。用人话讲生命周期钩子就像闹钟到点了就响。你只要提前设置好“闹钟响了干什么”Vue会在对应的时间点自动执行你的代码。二、Vue3生命周期钩子全家福Vue3组合式API里有这些钩子按执行顺序排列钩子函数执行时机常用程度setup组件初始化最先执行★★★★★onBeforeMount即将挂载到DOM★★☆☆☆onMounted已经挂载到DOM★★★★★onBeforeUpdate数据变了但DOM还没更新★★☆☆☆onUpdated数据变了DOM已更新★★☆☆☆onBeforeUnmount即将从DOM移除★★★★★onUnmounted已经从DOM移除★★☆☆☆onActivated被缓存后重新显示★★★☆☆onDeactivated被缓存后隐藏★★★☆☆下面我们一个一个用案例讲清楚。三、挂载阶段组件从无到有3.1 setup —— 一切的起点setup是所有逻辑开始的地方。在Vue3的script setup语法里你写的代码默认就是在setup里执行的。vuetemplate div p{{ greeting }}/p /div /template script setup // 这整个 script setup 块就是在 setup 函数里执行的 // setup 是最先执行的比所有生命周期钩子都早 import { ref } from vue // 1. 第一步定义响应式数据 // ref 创建一个响应式变量值是字符串 你好世界 const greeting ref(你好世界) // 2. 第二步定义方法 function sayHi() { console.log(setup 阶段定义的方法) } // 3. 这里可以打印一下验证 setup 最先执行 console.log(① setup 执行了组件开始初始化) // 此时组件还没挂载到页面上所以还看不到 DOM 元素 /script注意事项setup里不能操作 DOM因为组件还没挂上去适合做定义数据、定义方法、引入其他组件不适合做发网络请求虽然能做但更推荐在onMounted里做3.2 onBeforeMount —— 马上要挂载了这个钩子在组件挂载到页面之前触发。此时模板已经编译好了但还没插入到 DOM 里。vuetemplate div p refmyP我是一个段落/p /div /template script setup // 引入 onBeforeMount 钩子 import { onBeforeMount, ref } from vue // 创建一个 ref用来获取 DOM 元素 // ref(null) 初始值是 null后面会绑定到模板里的 p 元素上 const myP ref(null) // onBeforeMount 接收一个回调函数在挂载前执行 onBeforeMount(() { console.log(② onBeforeMount组件马上就要挂载了) // 尝试获取 DOM 元素 console.log(尝试获取 p 元素, myP.value) // 输出结果是 null因为还没挂载DOM 不存在 // 所以这个阶段不能操作 DOM什么都拿不到 }) /script注意事项不能操作 DOM因为组件还没插入页面这个钩子在服务端渲染SSR时也会执行所以适合做一些不依赖浏览器的初始化实际项目中这个钩子用得比较少3.3 onMounted —— 组件已经挂载好了重点这是最常用的钩子之一。组件已经插入到 DOM 里你现在可以安全地操作 DOM 元素、发网络请求、初始化第三方库了。vuetemplate div !-- 给 p 元素设置 refmyP方便在 JS 里获取它 -- p refmyP初始文本/p p用户名{{ username }}/p /div /template script setup // 引入 onMounted 钩子 import { onMounted, ref } from vue // 定义响应式数据 const username ref(加载中...) // 创建 ref 用来获取 p 元素 const myP ref(null) // onMounted 接收一个回调函数在挂载完成后执行 onMounted(() { console.log(③ onMounted组件已经挂载到页面上了) // 1. 现在可以安全操作 DOM 了 console.log(p 元素的内容, myP.value.textContent) // 输出初始文本 // 2. 可以修改 DOM myP.value.style.color red // 页面上的 p 元素文字变红了 // 3. 适合在这里发网络请求获取数据 // 模拟一个请求1秒后更新用户名 setTimeout(() { username.value 小明 // 页面会自动从 加载中... 变成 小明 }, 1000) // 实际项目里会这样写 // fetch(/api/user).then(res res.json()).then(data { // username.value data.name // }) }) /script注意事项可以安全操作 DOM最适合发初始数据请求的地方如果你的子组件也用了onMounted父组件的onMounted会在所有子组件挂载完成后才触发四、更新阶段数据变了页面跟着变4.1 onBeforeUpdate —— 数据变了但页面还是旧的当你修改了响应式数据Vue 会重新渲染 DOM。但在渲染之前会先触发onBeforeUpdate。vuetemplate div p refcountP计数{{ count }}/p button clickcount加1/button /div /template script setup // 引入 onBeforeUpdate 钩子 import { onBeforeUpdate, ref } from vue // 定义响应式数据初始值为 0 const count ref(0) // 获取 p 元素的引用 const countP ref(null) // onBeforeUpdate 在数据变化后、DOM 更新前触发 onBeforeUpdate(() { console.log(④ onBeforeUpdate数据变了但 DOM 还是旧的) // 此时 count.value 已经是新值了比如从 0 变成了 1 console.log(新的 count 值, count.value) // 输出 1 // 但 DOM 还没更新页面显示的还是旧值 console.log(页面上显示的, countP.value.textContent) // 输出计数0 —— 还是旧的 }) /script注意事项可以拿到更新前的 DOM 状态不要在这里修改数据可能导致无限循环实际项目中用得不多主要用于调试或特殊需求4.2 onUpdated —— DOM 已经更新完了数据变化导致 DOM 重新渲染完成后触发。vuetemplate div p refcountP计数{{ count }}/p button clickcount加1/button /div /template script setup // 引入 onUpdated 钩子 import { onUpdated, ref } from vue const count ref(0) const countP ref(null) // onUpdated 在 DOM 更新完成后触发 onUpdated(() { console.log(⑤ onUpdatedDOM 已经更新完成) // 现在 DOM 里显示的是新值了 console.log(页面上显示的, countP.value.textContent) // 输出计数1 —— 已经是新的了 }) // 注意onUpdated 会在每次数据变化导致 DOM 更新后都触发 // 所以不要在里面修改响应式数据否则会触发新一轮更新造成死循环 /script注意事项不要在这里修改响应式数据会导致无限更新循环如果需要根据 DOM 变化做操作比如重新初始化图表可以在这里做实际项目中用得不如onMounted多五、销毁阶段组件从页面移除5.1 onBeforeUnmount —— 组件即将被移除重点当组件因为v-if变成false或路由跳转而被移除时会先触发这个钩子。这是做清理工作的最佳时机。vuetemplate div p当前时间{{ now }}/p /div /template script setup // 引入 onBeforeUnmount 和 onMounted import { onBeforeUnmount, onMounted, ref } from vue const now ref() let timer null // 用来存放定时器的 ID // 组件挂载后开启一个定时器每秒更新时间 onMounted(() { console.log(挂载成功开启定时器) timer setInterval(() { // 每秒更新当前时间 now.value new Date().toLocaleTimeString() }, 1000) }) // 组件即将被销毁前必须清理定时器 onBeforeUnmount(() { console.log(⑥ onBeforeUnmount组件要销毁了赶紧清理) // 如果不清除定时器即使组件不在了定时器还会一直跑 // 会造成内存泄漏甚至报错 if (timer) { clearInterval(timer) // 清除定时器 timer null // 把变量也清掉防止后续误用 } }) /script注意事项必须在这里清理定时器、取消网络请求、移除事件监听如果不清理组件销毁后定时器还在跑会造成内存泄漏和onMounted是黄金搭档一个挂载时创建一个销毁前清理5.2 onUnmounted —— 组件已经销毁了组件从 DOM 移除后触发。此时组件已经完全不存在了。vuescript setup import { onUnmounted } from vue onUnmounted(() { console.log(⑦ onUnmounted组件已经彻底销毁) // 大部分清理工作建议在 onBeforeUnmount 里做 // 这个钩子主要是用于确认销毁或者做一些最终的日志记录 }) /script六、与 KeepAlive 配合的两个特殊钩子Vue 有个内置组件叫KeepAlive它可以把组件缓存起来切换走时不销毁切换回来时直接用缓存。被缓存的组件会多出两个钩子6.1 onActivated —— 组件被激活显示vuetemplate div我是被缓存的组件/div /template script setup import { onActivated } from vue // 被 KeepAlive 包裹的组件每次从缓存中恢复显示时触发 onActivated(() { console.log(组件从缓存中恢复了又能看到我了) // 可以在这里重新获取数据保证数据是最新的 // 比如fetchLatestData() }) /script6.2 onDeactivated —— 组件被失活隐藏vuescript setup import { onDeactivated } from vue // 被 KeepAlive 包裹的组件每次被缓存隐藏时触发 onDeactivated(() { console.log(组件被缓存了暂时隐藏) // 可以在这里暂停一些操作比如暂停播放器、停止轮询 // 比如pauseVideo() }) /script父组件配合 KeepAlive 使用vuetemplate div button clickshow !show切换显示/button !-- KeepAlive 包裹的组件不会被销毁而是被缓存 -- KeepAlive MyComponent v-ifshow / /KeepAlive /div /template script setup import { ref } from vue import MyComponent from ./MyComponent.vue const show ref(true) /script七、完整执行顺序当一个组件从创建到销毁不用KeepAlive完整顺序是text① setup —— 初始化数据和方法 ② onBeforeMount —— 即将挂载DOM不可用 ③ onMounted —— 挂载完成DOM可用可以发请求 ... 组件正常运行 ... ④ onBeforeUpdate —— 数据变了DOM还没更新 ⑤ onUpdated —— DOM更新完成 ... 可能多次触发 ④⑤ ... ⑥ onBeforeUnmount —— 组件即将销毁清理定时器/事件 ⑦ onUnmounted —— 组件已销毁记忆口诀创建先setup挂载前后跑更新数据变销毁要打扫。八、实战案例倒计时组件完整版用生命周期做一个功能完整的倒计时组件vuetemplate div classcountdown h3倒计时组件/h3 !-- 显示倒计时数字 -- p classnumber{{ countDown }}/p !-- 按钮区域 -- div classbuttons !-- 如果正在倒计时显示暂停按钮否则显示开始按钮 -- button clickisRunning ? pause() : start() {{ isRunning ? 暂停 : 开始 }} /button !-- 重置按钮回到60秒 -- button clickreset重置/button /div /div /template script setup // 引入需要的钩子和 API import { ref, onMounted, onBeforeUnmount } from vue // -------- 数据定义 -------- const countDown ref(60) // 倒计时从 60 秒开始 const isRunning ref(false) // 是否正在运行 let timer null // 定时器 ID初始为空 // -------- 方法定义 -------- // 开始倒计时 function start() { // 防止重复点击已经运行就不再创建新定时器 if (timer) return isRunning.value true // 标记为运行状态 // setInterval 返回一个定时器 ID用来后面清除 timer setInterval(() { // 每秒执行一次 if (countDown.value 0) { // 还没到 0减 1 countDown.value-- } else { // 到 0 了停止倒计时 clearInterval(timer) // 清除定时器 timer null // 清空变量 isRunning.value false // 标记为停止状态 } }, 1000) // 1000 毫秒 1 秒 } // 暂停倒计时 function pause() { if (timer) { clearInterval(timer) // 清除定时器 timer null // 清空变量 } isRunning.value false // 标记为停止状态 } // 重置倒计时 function reset() { // 先清除现有定时器 if (timer) { clearInterval(timer) timer null } countDown.value 60 // 重置为 60 秒 isRunning.value false // 停止状态 } // -------- 生命周期钩子 -------- // 组件挂载后自动开始倒计时 onMounted(() { console.log(倒计时组件挂载成功) start() // 自动开始倒计时 }) // 组件销毁前必须清除定时器 onBeforeUnmount(() { console.log(倒计时组件即将销毁清理定时器) // 如果不清除组件都没了定时器还在跑会内存泄漏 if (timer) { clearInterval(timer) timer null } }) /script style scoped .countdown { text-align: center; padding: 20px; border: 1px solid #ccc; border-radius: 8px; } .number { font-size: 48px; font-weight: bold; margin: 20px 0; } .buttons button { margin: 0 10px; padding: 8px 20px; cursor: pointer; } /style配合父组件使用用 v-if 控制销毁vuetemplate div button clickshowCountdown !showCountdown {{ showCountdown ? 销毁 : 显示 }}倒计时组件 /button !-- v-if 为 false 时组件被销毁触发 onBeforeUnmount -- Countdown v-ifshowCountdown / /div /template script setup import { ref } from vue import Countdown from ./Countdown.vue const showCountdown ref(true) /script九、练习题选择题以下哪个钩子在组件挂载到 DOM 之后触发A. setupB. onBeforeMountC. onMountedD. onBeforeUnmount发网络请求获取初始数据放在哪个钩子最合适A. setupB. onBeforeMountC. onMountedD. onUpdated组件销毁前清除定时器应该放在哪个钩子A. onMountedB. onBeforeUpdateC. onBeforeUnmountD. onUnmounted判断题每次组件的数据变化导致 DOM 更新onUpdated 都会触发。 onBeforeUnmount 在 onUnmounted 之后触发。 setup 阶段可以安全地操作 DOM 元素。 简答题为什么清除定时器要在 onBeforeUnmount 里做不在 onUnmounted 里做行不行编程题写一个实时时钟组件显示当前时间格式为HH:MM:SS组件挂载时开始计时每秒更新组件销毁时清除定时器改进题在题8的基础上增加一个“暂停/继续”按钮点击按钮切换暂停状态暂停时定时器清除继续时重新开启定时器记得在销毁时处理定时器十、答案C。onMounted 在 DOM 挂载完成后触发。C。onMounted 是最佳选择此时 DOM 已可用且符合用户预期先看到页面再加载数据。虽然 setup 里也能发请求但那时 DOM 还没出来。C。onBeforeUnmount 是清理工作的最佳时机组件还能正常使用但马上就要销毁了。onUnmounted 也行但没必要等到彻底销毁才清理。正确。只要响应式数据变化导致 DOM 重新渲染onUpdated 就会执行。错误。onBeforeUnmount 先执行销毁前然后才是 onUnmounted销毁后。错误。setup 阶段组件还没挂载无法获取 DOM 元素此时 ref 绑定的 DOM 值是 null。参考答案在 onBeforeUnmount 里做清理是最佳实践。此时组件还没销毁可以正常操作保证清理成功。在 onUnmounted 里做也行但没必要等到完全销毁再清理。关键是必须清理不然定时器会一直运行造成内存泄漏。参考实现vuetemplate div h2当前时间/h2 p stylefont-size: 36px;{{ currentTime }}/p /div /template script setup import { ref, onMounted, onBeforeUnmount } from vue // 定义响应式数据存储当前时间字符串 const currentTime ref() // 用来存定时器 ID let timer null // 更新时间的方法 function updateTime() { // 获取当前时间对象 const now new Date() // 获取小时、分钟、秒不足两位前面补 0 const hours String(now.getHours()).padStart(2, 0) const minutes String(now.getMinutes()).padStart(2, 0) const seconds String(now.getSeconds()).padStart(2, 0) // 拼接成 HH:MM:SS 格式 currentTime.value ${hours}:${minutes}:${seconds} } onMounted(() { // 先立即更新一次避免一开始显示空白 updateTime() // 然后每秒更新 timer setInterval(updateTime, 1000) }) onBeforeUnmount(() { // 销毁时清理定时器 if (timer) { clearInterval(timer) timer null } }) /script参考实现vuetemplate div h2实时时钟/h2 p stylefont-size: 36px;{{ currentTime }}/p !-- 按钮文字根据运行状态切换 -- button clicktoggle {{ isRunning ? 暂停 : 继续 }} /button /div /template script setup import { ref, onMounted, onBeforeUnmount } from vue const currentTime ref() const isRunning ref(false) // 默认暂停状态 let timer null function updateTime() { const now new Date() const hours String(now.getHours()).padStart(2, 0) const minutes String(now.getMinutes()).padStart(2, 0) const seconds String(now.getSeconds()).padStart(2, 0) currentTime.value ${hours}:${minutes}:${seconds} } // 开启定时器 function startTimer() { // 防止重复创建定时器 if (timer) return updateTime() // 先立即更新一次 timer setInterval(updateTime, 1000) isRunning.value true } // 暂停定时器 function stopTimer() { if (timer) { clearInterval(timer) timer null } isRunning.value false } // 切换暂停/继续 function toggle() { if (isRunning.value) { stopTimer() } else { startTimer() } } // 挂载后自动开始 onMounted(() { startTimer() }) // 销毁前清理 onBeforeUnmount(() { if (timer) { clearInterval(timer) timer null } }) /script写在最后生命周期钩子是 Vue 的骨架搞懂了它你就知道每个阶段该干什么初始化数据在setup发请求、操作 DOM 在onMounted清理定时器、取消请求在onBeforeUnmount刚学不用死记硬背多写几个组件尤其是带定时器、带请求的自然就记住了。有疑问评论区找我下篇见