前言:很多人学 Vue3 生命周期只停留在“onMounted 操作 DOM、onUnmounted 清除定时器”,但真正决定项目稳定性、性能、可维护性的,是生命周期执行机制、副作用清理模型、setup 内部执行上下文、与渲染流程的关系。本文从原理到实战,把 Vue3 生命周期讲透,适合中高级前端直接用于项目架构与避坑。

一、先破误区:Vue3 生命周期不是“换个名字”那么简单

很多迁移文章只给对照表,却不说本质变化

  1. Vue2 生命周期 = 选项式 + 实例阶段强绑定
  2. Vue3 生命周期 = 组合式 + 副作用调度模型
  3. setup 不是一个生命周期,而是整个组合式 API 的执行入口
  4. onMounted 本质是“调度任务”,不是简单回调

理解这 4 点,才算真正入门 Vue3 生命周期。

二、Vue3 完整生命周期执行时序(最硬核的一张执行链)

组件从创建到销毁完整流程

setup 执行
   ↓
onBeforeMount
   ↓
【模板编译 / 渲染函数执行 / 生成 VNode Tree】
   ↓
【DOM 挂载、patch 完成】
   ↓
onMounted
   ↓
────────────────────
数据更新触发:
onBeforeUpdate
   ↓
【DOM diff + 重新渲染】
   ↓
onUpdated
────────────────────
   ↓
组件卸载触发:
onBeforeUnmount
   ↓
【DOM 移除、子树卸载、副作用清理】
   ↓
onUnmounted

关键结论(深度考点)

  1. setup 执行最早,早于所有生命周期
  2. onMounted 是在 DOM 真实插入页面后才执行
  3. onBeforeUpdate 执行时,DOM 还是旧的
  4. onUpdated 执行时,DOM 已经是新的,但可能还未完成浏览器重绘
  5. onUnmounted 触发时,Vue3 会自动清理当前组件实例关联的所有响应式 effect(包括 watch、computed 的依赖追踪 effect),但手动创建的定时器、全局事件监听、第三方库实例等「非 Vue 内置副作用」,仍需手动清理

三、Vue2 → Vue3 生命周期深度对比(不只对应,而是原理差异)

阶段 Vue2 Vue3 组合式 核心差异
实例初始化 beforeCreate setup 开头 Vue3 无实例 this,全部基于闭包与副作用
数据就绪 created setup 内部 setup 同步代码 = 天然替代 created
挂载前 beforeMount onBeforeMount Vue3 此时 VNode 已生成,但未插入 DOM
挂载完成 mounted onMounted Vue3 是微任务调度,执行时机更稳定
更新前 beforeUpdate onBeforeUpdate 此时视图未更新
更新完成 updated onUpdated 可能触发多次,慎用
卸载前 beforeDestroy onBeforeUnmount 改名+语义更清晰
卸载完成 destroyed onUnmounted 自动清理 effect,Vue2 需手动
缓存组件 activated onActivated 只在 keep-alive 内生效

四、核心原理:为什么 Vue3 要把生命周期设计成“函数调用”?

1. 脱离 this 耦合

Vue2 生命周期强依赖 this,导致逻辑无法抽离成独立函数。Vue3 通过副作用收集机制,让生命周期可以写在任何函数里(包括 hooks)。

2. 支持“多实例隔离”

同一个 hook 可以在组件内多次调用,不会互相污染。

3. 与响应式系统深度绑定

所有生命周期本质都是:在渲染流程的特定时机,插入一段 effect 任务

这也是为什么:

  • onMounted 能保证 DOM 存在
  • onUnmounted 能自动清理 Vue 内置副作用
  • watch 本质也是一种特殊生命周期

五、生产级标准写法(无冗余、可直接复制进项目)

5.1 最标准的 script setup 生命周期模板

<template>
  <div ref="chartRef" class="demo-box"></div>
</template>

<script setup>
import {
  ref,
  onBeforeMount,
  onMounted,
  onBeforeUpdate,
  onUpdated,
  onBeforeUnmount,
  onUnmounted,
  onActivated,
  onDeactivated
} from 'vue'
import { useRequest } from '@/hooks/useRequest'
import { initChart, destroyChart } from '@/utils/echarts'

// 响应式
const chartRef = ref(null)
let timer = null
let resizeObserver = null

// ==========================================
// 对应 beforeCreate + created
// ==========================================
console.log('setup 执行:组件初始化阶段')
const { data, loading } = useRequest('/api/list')

// ==========================================
// 挂载前:准备配置
// ==========================================
onBeforeMount(() => {
  console.log('onBeforeMount:VNode 已生成,DOM 未挂载')
})

// ==========================================
// 挂载后:DOM 操作、第三方库、事件绑定
// ==========================================
onMounted(() => {
  console.log('onMounted:DOM 已挂载')

  // 初始化图表
  if (chartRef.value) {
    initChart(chartRef.value)
  }

  // 定时器
  timer = setInterval(() => { /* 业务逻辑 */ }, 1000)

  // 监听窗口变化
  resizeObserver = new ResizeObserver(entries => {
    // 图表自适应
  })
  resizeObserver.observe(chartRef.value)
})

// ==========================================
// 更新前后(一般业务极少使用)
// ==========================================
onBeforeUpdate(() => {
  // 旧 DOM 状态
})
onUpdated(() => {
  // 新 DOM 已渲染
})

// ==========================================
// keep-alive 缓存组件生命周期
// ==========================================
onActivated(() => {
  console.log('组件从缓存激活')
})
onDeactivated(() => {
  console.log('组件进入缓存')
})

// ==========================================
// 卸载前:必须清理!防止内存泄漏
// ==========================================
onBeforeUnmount(() => {
  console.log('onBeforeUnmount:组件即将卸载')

  // 清理定时器(手动创建的非Vue内置副作用)
  if (timer) clearInterval(timer)

  // 清理 ResizeObserver / 事件监听(手动创建的非Vue内置副作用)
  if (resizeObserver) {
    resizeObserver.disconnect()
  }

  // 销毁第三方实例(手动创建的非Vue内置副作用)
  if (chartRef.value) {
    destroyChart(chartRef.value)
  }
})

onUnmounted(() => {
  console.log('onUnmounted:组件已完全卸载')
})
</script>

六、真正高级的部分:生命周期底层机制(文章深度核心)

6.1 onMounted 到底是怎么保证 DOM 存在的?

Vue3 在 patch 流程完成 DOM 插入后,会执行:

queuePostRenderEffect(() => {
  // 所有 onMounted 回调在这里执行
})

它是一个微任务队列,保证:

  • 所有子组件挂载完成
  • 所有 DOM 插入结束
  • 同步代码执行完毕

所以你在 onMounted 里一定能拿到 ref DOM。

6.2 onUnmounted 为什么能自动清理?

因为 Vue3 对每个组件维护了:组件实例的副作用链表(effects),卸载时会遍历执行清理函数,仅针对 Vue 内置副作用:

  • watch
  • computed
  • 生命周期回调
  • 自定义 effect

这是 Vue2 不具备的自动内存管理机制,但手动创建的副作用仍需手动清理。

6.3 为什么不要在 setup 同步代码里操作 DOM?

setup 执行时:

  • 没有 DOM
  • 没有渲染
  • ref 都是 null
  • 模板还未生成 VNode

操作 DOM 必然报错。这是初级与中级开发者的核心区别。

七、企业级高频坑(90% 项目都踩过)

坑 1:onMounted 里获取 ref 依然为 null

原因

  • v-if 控制的元素
  • 父组件 suspense
  • 动态组件 + keep-alive

生产解决方案

watch(
  () => chartRef.value,
  (val) => {
    if (val) initChart(val)
  },
  { immediate: true }
)

坑 2:onUpdated 无限循环更新

原因:在 onUpdated 中修改了触发更新的响应式数据。

规范:onUpdated 只做读取,不做写入

坑 3:定时器/事件监听泄漏

根本原因:没有理解 Vue3 卸载机制仅自动清理内置副作用,不会自动清理全局对象、手动创建的副作用。

工程化最佳实践:封装 useTimeruseEventListeneruseResize 等自动清理 hooks。

坑 4:keep-alive 组件重复请求接口

原因:mounted 只执行一次,后续进入不会触发。

解决方案:接口放在 onActivated

八、架构级最佳实践(真正拉开差距的内容)

1. 生命周期职责严格分层

  • setup:数据初始化、请求封装
  • onBeforeMount:配置准备
  • onMounted:DOM、第三方库、监听
  • onBeforeUnmount:统一清理手动副作用
  • onUpdated:禁止写业务逻辑

2. 所有副作用必须封装成 Hook

// useChart.js
export function useChart(elRef) {
  let chart = null
  onMounted(() => { chart = init(elRef.value) })
  onBeforeUnmount(() => { chart?.dispose() })
  return { chart }
}

3. 禁止在生命周期中写复杂业务逻辑

复杂逻辑抽离 → composables,生命周期只负责“时机调度”。

4. 统一错误边界与异常捕获

onMounted(async () => {
  try { /* ... */ }
  catch (err) {
    console.error(err)
  }
})

九、总结

Vue3 生命周期不是简单的 API 改名,而是从“选项式阶段模型”升级为“副作用调度模型”

真正掌握生命周期,要理解三件事:

  1. 执行时机与渲染流程的关系
  2. 副作用自动清理机制(区分内置与手动副作用)
  3. setup 与 hooks 的架构意义

只有从原理层面理解,才能写出无内存泄漏、高性能、可维护、可迁移的企业级 Vue3 项目。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐