Vue 3 生命周期完全指南:从流程图到最佳实践

Vue 3 生命周期完全指南:从流程图到最佳实践 -- pd的前端笔记

文章目录

    • [Vue 3 生命周期完全指南:从流程图到最佳实践 -- pd的前端笔记](#Vue 3 生命周期完全指南:从流程图到最佳实践 -- pd的前端笔记)
  • 生命周期流程图深度解析
  • [12 个生命周期钩子函数详解(组合式 API 版)](#12 个生命周期钩子函数详解(组合式 API 版))
    • [✅ 核心三剑客(90% 场景用它们)](#✅ 核心三剑客(90% 场景用它们))
    • [🟡 进阶钩子(特定场景使用)](#🟡 进阶钩子(特定场景使用))
    • [🔴 调试与高级钩子(慎用!)](#🔴 调试与高级钩子(慎用!))
      • onErrorCaptured
      • [onRenderTracked 和 onRenderTriggered](#onRenderTracked 和 onRenderTriggered)
    • [🟣 KeepAlive 专属钩子(缓存组件专用)](#🟣 KeepAlive 专属钩子(缓存组件专用))
      • [onActivated 和 onDeactivated](#onActivated 和 onDeactivated)
    • [SSR 专属钩子](#SSR 专属钩子)
  • [常见误区 & 最佳实践](#常见误区 & 最佳实践)
    • [❌ 误区 1:在 setup() 外异步调用钩子](#❌ 误区 1:在 setup() 外异步调用钩子)
      • [setTimeout 的正确打开方式](#setTimeout 的正确打开方式)
        • [✅ 场景 1:等待 DOM 完全渲染(尤其是涉及图片、字体加载时)](#✅ 场景 1:等待 DOM 完全渲染(尤其是涉及图片、字体加载时))
        • [✅ 场景 2:防抖(Debounce)与简单延时](#✅ 场景 2:防抖(Debounce)与简单延时)
      • [比 setTimeout 更好的选择 ------ nextTick](#比 setTimeout 更好的选择 —— nextTick)
    • [❌ 误区 2:在 updated 中修改数据导致死循环](#❌ 误区 2:在 updated 中修改数据导致死循环)
    • [✅ 最佳实践 1:清理副作用](#✅ 最佳实践 1:清理副作用)
    • [✅ 最佳实践 2:封装可复用逻辑(组合式函数)](#✅ 最佳实践 2:封装可复用逻辑(组合式函数))
  • 四、实战场景速查表

生命周期流程图深度解析

流程图关键节点拆解:

1️⃣ 渲染器遇到组件 → 组件实例开始创建

  • 这是生命周期的起点,Vue 内部开始初始化组件
  • 此时还没执行任何用户代码

2️⃣ setup() / beforeCreate → "胎儿期"

  • setup() 是组合式 API 的入口,最早执行的钩子
  • beforeCreate 在选项式 API 中对应,此时实例刚创建,数据观测和事件都还没设置
  • ⚠️ 注意:setup() 中无法访问 this,因为实例还没完全初始化

3️⃣ 初始化选项式 API → created

  • 数据观测、计算属性、方法等已设置完成
  • 可以访问 data、computed、methods
  • ❗但 DOM 还没生成,不能操作 DOM

4️⃣ 模板编译阶段

  • 判断是否有预编译模板(.vue 文件通常有)
  • 没有则即时编译(运行时编译,性能稍差)
  • 这个阶段用户一般不干预

5️⃣ beforeMount → "出生前一刻"

  • 模板已编译成渲染函数,但还没挂载到 DOM
  • 可以最后一次修改响应式数据,不会触发额外更新

6️⃣ mounted → "正式出生" 🎉

  • DOM 节点已创建并插入页面
  • ✅ 可以安全操作 DOM、发起异步请求、初始化第三方库(如图表、地图)
  • 最常用钩子之一

7️⃣ 挂载后循环:数据变化 → beforeUpdate → 重新渲染 → updated

  • 当响应式数据变化时,触发更新流程
  • beforeUpdate:DOM 还没更新,可获取更新前的 DOM
  • updated:DOM 已更新完成,可操作更新后的 DOM
  • ⚠️ 避免在 updated 中修改数据,否则会无限循环!

8️⃣ 卸载阶段:beforeUnmount → unmounted

  • 组件被移除(如 v-if="false" 或路由切换)
  • beforeUnmount:清理定时器、取消订阅、解绑事件
  • unmounted:组件已彻底销毁,无法再访问

12 个生命周期钩子函数详解(组合式 API 版)

💡 所有钩子都从 'vue' 导入,命名规范:on + 钩子名(首字母大写)

✅ 核心三剑客(90% 场景用它们)

onMounted

ts 复制代码
import { onMounted } from 'vue'

onMounted(() => {
  console.log('组件已挂载到 DOM')
  // ✅ 最佳实践场景:
  // - 发起 API 请求
  // - 初始化 ECharts / Three.js 等重型库
  // - 绑定 window 事件(记得在 onUnmounted 解绑!)
})

onUpdated

ts 复制代码
import { onUpdated } from 'vue'

onUpdated(() => {
  console.log('组件因数据变化而重新渲染')
  // ⚠️ 警告:不要在这里修改响应式数据!
  // ✅ 适用场景:
  // - 更新后需要重新计算 DOM 尺寸(如滚动位置)
  // - 同步第三方库状态(如更新图表数据)
})

onUnmounted

ts 复制代码
import { onUnmounted } from 'vue'

onUnmounted(() => {
  console.log('组件即将被销毁')
  // ✅ 必做清理工作:
  // - clearInterval / clearTimeout
  // - removeEventListener
  // - 取消 Axios 请求(使用 AbortController)
  // - 断开 WebSocket 连接
})

🟡 进阶钩子(特定场景使用)

onBeforeMount

ts 复制代码
// 很少用,除非你需要在 DOM 渲染前做最后的数据调整
onBeforeMount(() => {
  // 此时模板已编译,但 DOM 未生成
  // 修改数据不会触发额外更新
})

onBeforeUpdate

ts 复制代码
// 用于在 DOM 更新前访问旧状态
onBeforeUpdate(() => {
  const oldDom = document.querySelector('.my-element')
  console.log('更新前的 DOM:', oldDom)
})

onBeforeUnmount

ts 复制代码
// 和 onUnmounted 类似,但更早一点
// 通常两者选其一即可,推荐用 onUnmounted
onBeforeUnmount(() => {
  // 清理工作
})

🔴 调试与高级钩子(慎用!)

onErrorCaptured

ts 复制代码
// 捕获子组件抛出的错误
import { onErrorCaptured } from 'vue'

onErrorCaptured((err, instance, info) => {
  console.error('捕获到错误:', err)
  console.log('错误来源组件:', instance)
  console.log('错误信息:', info)
  
  // 返回 false 阻止错误继续向上传播
  return false
})

onRenderTracked 和 onRenderTriggered

ts 复制代码
// 🚨 仅用于调试!生产环境不要使用
// 追踪哪些响应式数据触发了渲染

onRenderTracked((event) => {
  console.log('追踪到依赖:', event.key)
})

onRenderTriggered((event) => {
  console.log('触发渲染的数据:', event.key)
})

🟣 KeepAlive 专属钩子(缓存组件专用)

onActivated 和 onDeactivated

ts 复制代码
import { onActivated, onDeactivated } from 'vue'

// 只在使用 <KeepAlive> 包裹组件时生效
onActivated(() => {
  console.log('组件被激活(从缓存恢复)')
  // 重置定时器、重新请求数据等
})

onDeactivated(() => {
  console.log('组件被停用(进入缓存)')
  // 暂停定时器、保存状态等
})

💡 典型场景:Tab 切换、路由缓存( 配合 keep-alive)

SSR 专属钩子

onServerPrefetch

ts 复制代码
// 仅在服务端渲染时执行
import { onServerPrefetch } from 'vue'

onServerPrefetch(async () => {
  // 在服务端预取数据
  const data = await fetch('/api/data').then(r => r.json())
  // 赋值给响应式变量,服务端渲染时会包含这些数据
})

常见误区 & 最佳实践

❌ 误区 1:在 setup() 外异步调用钩子

ts 复制代码
// 错误示范!
setTimeout(() => {
  onMounted(() => { ... }) // 无效!组件实例已丢失
}, 1000)

// ✅ 正确做法:同步注册
onMounted(() => { ... })

setTimeout 的正确打开方式

  • setTimeout 函数 用来指定某个函数或某段代码,在多少毫秒之后执行,返回一个整数,表示定时器的编号,以后可以用来取消这个定时器
  • 接受两个参数, 第一个是要延迟执行的函数名或者代码,第二个参数是推迟执行的毫秒数
  • 注意:this在定时器中指向window

接下来,对错误示范中的执行顺序进行说明。

🕵️‍♂️ 破案过程:

  1. 组件初始化阶段:
    • Vue 开始创建组件实例。
    • 执行 setup() 函数。
    • 关键点:Vue 会在 setup() 同步执行完毕的那一刻,收集所有已注册的生命周期钩子(比如你在第一行直接写的 onMounted(...))。
    • 此时,setTimeout 被放入浏览器的宏任务队列,等待 1 秒后执行。setup() 函数继续往下走,然后结束。
  2. 挂载阶段:
    • Vue 检查收集到的钩子列表,发现有一个 onMounted。
    • 执行 DOM 挂载。
    • 挂载完成后,触发那个早已注册好的 onMounted 回调。
    • 组件状态变为"已挂载"。
  3. 1 秒后:
    • setTimeout 的回调终于执行了。
    • 代码试图调用 onMounted(...)。
    • 灾难发生:此时 setup() 的上下文(Context)已经结束了,Vue 内部无法知道这个新的 onMounted 应该绑定到哪个组件实例上(或者说,组件已经过了"注册钩子"的阶段,甚至可能已经挂载完了)。
    • 结果:这个回调要么被忽略,要么绑定到了一个错误的/空的上下文,永远不会被触发。

虽然不能在 setTimeout 里注册钩子,但我们在 onMounted 等钩子内部经常使用 setTimeout。它是处理时序问题的神器。

✅ 场景 1:等待 DOM 完全渲染(尤其是涉及图片、字体加载时)

有时候,onMounted 触发了,DOM 节点虽然插入了,但浏览器还没来得及计算布局(Layout),或者图片还没加载出来,导致获取到的宽高为 0。

js 复制代码
import { onMounted, ref } from 'vue'

const width = ref(0)
const myRef = ref(null)

onMounted(() => {
  // ❌ 直接获取可能为 0,因为浏览器重绘还没完成
  // width.value = myRef.value.offsetWidth 

  // ✅ 使用 setTimeout 推迟到下一个事件循环
  setTimeout(() => {
    width.value = myRef.value.offsetWidth
    console.log('获取到的真实宽度:', width.value)
  }, 0) // 延迟 0ms 即可,目的是放入宏任务队列
})

原理:setTimeout(..., 0) 会将回调放入宏任务队列,等到当前所有同步代码(包括 Vue 的渲染流程)都执行完后,浏览器完成了重绘,再执行这个回调。

✅ 场景 2:防抖(Debounce)与简单延时

在处理搜索输入、窗口大小调整时,避免频繁触发逻辑。

js 复制代码
import { ref, onUnmounted } from 'vue'

let timer = null

const handleSearch = (e) => {
  clearTimeout(timer) // 清除上一次的定时器
  timer = setTimeout(() => {
    console.log('发起搜索请求:', e.target.value)
    // api.search(...)
  }, 500) // 用户停止输入 500ms 后才执行
}

onUnmounted(() => {
  // ⚠️ 重要:组件销毁时必须清除定时器,防止内存泄漏
  if (timer) clearTimeout(timer)
})

比 setTimeout 更好的选择 ------ nextTick

在 Vue 中,如果你想等待 DOM 更新,首选方案不是 setTimeout,而是 nextTick。

特性 setTimeout(fn, 0) await nextTick()
执行时机 宏任务队列(较晚) 微任务队列(紧跟在当前同步代码后,DOM 更新完立即执行)
精确度 受浏览器最小延迟限制(通常 4ms+) 精确对应 Vue 的 DOM 更新周期
性能 稍慢,需要等待事件循环一圈 更快,无需等待宏任务
语义 "过一会儿执行" "等 DOM 更新完立刻执行"
推荐度 ⭐⭐ (通用 JS 方案) ⭐⭐⭐⭐⭐ (Vue 官方推荐)
js 复制代码
import { ref, onMounted, nextTick } from 'vue'

const count = ref(0)
const message = ref('初始')

const updateAndLog = async () => {
  count.value++
  message.value = '更新后的内容'
  
  // ❌ 不推荐:不知道要等多久,可能太早也可能太晚
  // setTimeout(() => { ... }, 0) 

  // ✅ 推荐:等待 Vue 完成 DOM 更新
  await nextTick()
  
  // 此时 DOM 绝对已经是最新的状态了
  console.log(document.querySelector('.msg').textContent) 
}

❌ 误区 2:在 updated 中修改数据导致死循环

ts 复制代码
// 危险!
onUpdated(() => {
  count.value++ // 触发更新 → 再次调用 updated → 无限循环
})

// ✅ 解决方案:加条件判断
onUpdated(() => {
  if (shouldUpdate) {
    // 安全操作
  }
})

✅ 最佳实践 1:清理副作用

ts 复制代码
let timer = null

onMounted(() => {
  timer = setInterval(() => { ... }, 1000)
})

onUnmounted(() => {
  clearInterval(timer) // 必须清理!
})

✅ 最佳实践 2:封装可复用逻辑(组合式函数)

js 复制代码
// useMousePosition.js
import { ref, onMounted, onUnmounted } from 'vue'

export function useMousePosition() {
  const x = ref(0)
  const y = ref(0)

  const update = (e) => {
    x.value = e.pageX
    y.value = e.pageY
  }

  onMounted(() => {
    window.addEventListener('mousemove', update)
  })

  onUnmounted(() => {
    window.removeEventListener('mousemove', update)
  })

  return { x, y }
}

// 组件中使用
const { x, y } = useMousePosition()

四、实战场景速查表

场景 推荐钩子 说明
发起 API 请求 onMounted DOM 已就绪,可显示 loading
操作 DOM onMounted 确保元素存在
清理定时器/事件 onUnmounted 防止内存泄漏
监听窗口大小变化 onMounted + onUnmounted 绑定和解绑事件
缓存组件状态恢复 onActivated KeepAlive 场景
调试渲染性能 onRenderTracked 开发环境专用
服务端预取数据 onServerPrefetch SSR 项目专用
相关推荐
耀耀切克闹灬1 小时前
前端签章数据的模板处理
前端
掘金安东尼2 小时前
⏰前端周刊第 454 期(2026年2月16日-2月22日)
前端·javascript·面试
掘金安东尼2 小时前
⏰前端周刊第 453 期(2026年2月9日-2月15日)
前端·javascript·面试
Amumu121382 小时前
CSS进阶导读
前端·css
anyup2 小时前
uniapp开发的鸿蒙应用上架后,竟然月入4000+
前端·vue.js·harmonyos
无尽的沉默2 小时前
使用Thymeleaf配置国际化页面(语言切换)
前端·spring boot
代码老中医2 小时前
接手老项目的一个月,我重构了那个2000行的“祖传”React组件
前端
用户83040713057013 小时前
外链跳转后首页参数丢失:从缓存兜底到页面重加载的完整方案
vue.js
用户83040713057013 小时前
路由传参刷新丢失问题:三种解决方案与最佳实践
前端