一、前言
组件和人一样有完整的一生:出生(创建挂载)→ 成长(更新)→ 离世(卸载)。在每个关键节点,你都能"注册一个动作":挂载后发请求、卸载前清定时器。这些节点就是生命周期钩子。写代码最容易犯的错就是"时机不对"------页面还 没 DOM 就去操作它。今天把时机一次理顺。
二、一张人生时间轴看懂执行顺序
<script setup> 执行 ← 相当于 Vue2 的 beforeCreate + created,最早的时机
↓
onBeforeMount() ← 模板编译完成,还没渲染出真实 DOM
↓
onMounted() ← ✅ DOM 已就绪!发请求、拿 ref、初始化图表都在这
↓ (数据变化,可循环多次)
onBeforeUpdate() → 真实 DOM 更新 → onUpdated()
↓ (调用卸载 / 切走页面)
onBeforeUnmount() ← 组件还健在,收尾动作放这(清定时器、取消订阅)
↓
onUnmounted() ← 彻底离世
只写常用的四个就够 90% 场景:setup、onMounted、onBeforeUnmount、onUnmounted。
三、基本用法
全部从 vue 导入,在 <script setup> 里直接调用注册:
vue
<template>
<p ref="box">{{ msg }}</p>
<button @click="msg = '更新了'">更新</button>
</template>
<script setup>
import { ref, onBeforeMount, onMounted, onBeforeUpdate, onUpdated, onBeforeUnmount, onUnmounted } from 'vue'
const msg = ref('hello')
const box = ref(null)
console.log('1. setup 执行(最早,比所有钩子都早)')
onBeforeMount(() => console.log('2. onBeforeMount:还没 DOM'))
onMounted(() => {
console.log('3. onMounted:DOM 就绪')
console.log('拿到 DOM 元素:', box.value) // ✅ 这里 ref 才有值
})
onBeforeUpdate(() => console.log('4. onBeforeUpdate:数据变了,DOM 还没变'))
onUpdated(() => console.log('5. onUpdated:DOM 也更新完了'))
onBeforeUnmount(() => console.log('6. onBeforeUnmount:临终遗嘱时间'))
onUnmounted(() => console.log('7. onUnmounted:彻底卸载'))
</script>
打开控制台点一点,每个节点的先后顺序一目了然,比背八遍文档都记得牢。
四、Vue2 → Vue3 对照表(老项目迁移必存)
Vue3 组合式 API 里钩子全部加 on 前缀,有两个直接改名:
| Vue2(选项式) | Vue3(组合式) | 说明 |
|---|---|---|
| beforeCreate | 不需要了 | <script setup> 本身就是 |
| created | 不需要了 | 同上,setup 执行时机等价 |
| beforeMount | onBeforeMount | |
| mounted | onMounted | |
| beforeUpdate | onBeforeUpdate | |
| updated | onUpdated | |
| beforeDestroy | onBeforeUnmount | 改名了 |
| destroyed | onUnmounted | 改名了 |
记住结论:created 里干的事(初始化数据、发请求),Vue3 直接写在 setup 顶层,不用包任何钩子。
五、综合实战:时钟组件(正确时机演示)
一个会走秒的时钟,挂载后启动,卸载前销毁------把最经典的"开/关定时器"时机写对:
vue
<template>
<p>当前时间:{{ now }}</p>
</template>
<script setup>
import { ref, onMounted, onBeforeUnmount } from 'vue'
const now = ref('')
let timer = null
// 挂载后:启动定时器
onMounted(() => {
timer = setInterval(() => {
now.value = new Date().toLocaleTimeString()
}, 1000)
})
// 卸载前:清掉定时器
onBeforeUnmount(() => {
clearInterval(timer)
timer = null
})
</script>
使用它的父组件加个开关按钮,切走再切回,控制台不会有任何定时器泄漏------onMounted 开的,onBeforeUnmount 必须关,成对出现是铁律。
【截图位置:时钟每秒走字的运行效果】
顺便看一个高频问题------父子组件的 mounted 顺序:父组件里引用子组件时,子组件的 onMounted 先执行,父组件的后执行(儿子先出生,爸爸才能算"生完了")。父组件要在 onMounted 里拿子组件内容时记住这条。
六、踩坑记录
- 在 setup 顶层直接操作 DOM / 用 ref :
box.value是 null------DOM 还没生成,必须放 onMounted 里 - 定时器/事件监听忘清理:组件切走了 setInterval 还在后台跑,页面越用越卡(内存泄漏)。开与关必须成对
- 把发请求包在 created 心态里套钩子:Vue3 直接写 setup 顶层即可,等价于 created,还少一层嵌套
- onMounted 是异步的不用 await:需要"请求完再渲染"用响应式数据 + v-if 控制骨架屏,而不是阻塞钩子
- async setup 会白屏 :
<script setup>顶层直接 await 需要配 Suspense(032 篇细讲),新手期别在 setup 顶层裸 await - 在钩子里用箭头函数丢了 this:组合式 API 已经没有 this 了,全员箭头函数即可------这也是 Vue3 比 Vue2 心智负担小的地方
七、今日小结
- 人生时间轴:setup(最早)→ onMounted(DOM 就绪)→ 更新钩子 → onBeforeUnmount → onUnmounted
- 发请求、拿 ref、初始化第三方库 → onMounted
- 清定时器、解绑事件 → onBeforeUnmount,与开启动作成对出现
- Vue2 的 created 逻辑 = Vue3 的 setup 顶层;beforeDestroy/destroyed 改名为 onBeforeUnmount/onUnmounted
作者:陈志宝
下篇预告
还剩最后一块拼图:005、006 反复埋的"解构丢响应式"到底怎么破?下一篇 toRef 与 toRefs------响应式数据的"解构救星",也是通往组合式函数的最后一站。