文章目录
- 前言
- 一、生命周期概览
-
- [1.1 Vue 3 完整生命周期](#1.1 Vue 3 完整生命周期)
- [1.2 Vue 2 → Vue 3 钩子更名](#1.2 Vue 2 → Vue 3 钩子更名)
- [1.3 Options API vs Composition API](#1.3 Options API vs Composition API)
- 二、各钩子详解
-
- [2.1 setup(替代 beforeCreate / created)](#2.1 setup(替代 beforeCreate / created))
- [2.2 onBeforeMount](#2.2 onBeforeMount)
- [2.3 onMounted](#2.3 onMounted)
- [2.4 onBeforeUpdate / onUpdated](#2.4 onBeforeUpdate / onUpdated)
- [2.5 onBeforeUnmount / onUnmounted](#2.5 onBeforeUnmount / onUnmounted)
- [2.6 nextTick 与生命周期的关系](#2.6 nextTick 与生命周期的关系)
- [2.7 其他钩子(了解即可)](#2.7 其他钩子(了解即可))
- 三、错误处理钩子
-
- [3.1 onErrorCaptured](#3.1 onErrorCaptured)
- [3.2 应用场景](#3.2 应用场景)
- [四、keep-alive 特殊生命周期](#四、keep-alive 特殊生命周期)
-
- [4.1 onActivated / onDeactivated](#4.1 onActivated / onDeactivated)
- [4.2 与 onMounted / onUnmounted 的区别](#4.2 与 onMounted / onUnmounted 的区别)
- 五、父子组件执行顺序
-
- [5.1 挂载顺序](#5.1 挂载顺序)
- [5.2 卸载顺序](#5.2 卸载顺序)
- [5.3 更新顺序](#5.3 更新顺序)
- 六、常见应用场景
-
- [6.1 数据请求](#6.1 数据请求)
- [6.2 事件监听与清理](#6.2 事件监听与清理)
- [6.3 定时器管理](#6.3 定时器管理)
- [6.4 第三方库初始化](#6.4 第三方库初始化)
- [6.5 watch 清理与 onUnmounted](#6.5 watch 清理与 onUnmounted)
- 七、面试聚焦
-
- [7.1 为什么父组件 onMounted 在子组件之后?](#7.1 为什么父组件 onMounted 在子组件之后?)
- [7.2 onUpdated 和 nextTick 有什么区别?](#7.2 onUpdated 和 nextTick 有什么区别?)
- [7.3 注册多个 onMounted 的执行顺序?](#7.3 注册多个 onMounted 的执行顺序?)
- [7.4 async setup 会影响生命周期吗?](#7.4 async setup 会影响生命周期吗?)
- 八、易混淆点
- 九、思考与练习
- 总结
前言
生命周期钩子是 Vue 组件从创建到销毁各个阶段自动调用的函数,理解它们的执行时机和应用场景是写出健壮代码的基础。本篇会讲清楚:
- 各生命周期钩子的执行时机
- Composition API 中的生命周期
- 父子组件生命周期执行顺序(挂载 / 更新 / 卸载)
- keep-alive 缓存组件的特殊生命周期
- nextTick 与生命周期的配合
一、生命周期概览
1.1 Vue 3 完整生命周期
setup()
↓
onBeforeMount() // DOM 挂载前
↓
onMounted() // DOM 挂载后
↓
┌─────────────────────────────────────┐
│ 响应式数据变化时重复进入(非一次性) │
│ onBeforeUpdate() → onUpdated() │
└─────────────────────────────────────┘
↓
onBeforeUnmount() // 组件卸载前
↓
onUnmounted() // 组件卸载后
挂载与卸载各执行一次;更新钩子会在每次响应式依赖触发的重渲染中重复触发。
1.2 Vue 2 → Vue 3 钩子更名
| Vue 2 | Vue 3 |
|---|---|
beforeDestroy |
beforeUnmount |
destroyed |
unmounted |
其余钩子名称基本不变。Vue 3 新增 onActivated / onDeactivated(配合 keep-alive)、onErrorCaptured 等。
1.3 Options API vs Composition API
javascript
// Options API(Vue 2 风格,Vue 3 仍可用)
export default {
beforeCreate() { }, // Composition API 中由 setup 替代,Options API 仍可用
created() { }, // 同上;setup 中的同步代码等价于 created
beforeMount() { },
mounted() { },
beforeUpdate() { },
updated() { },
beforeUnmount() { },
unmounted() { }
}
// Composition API(Vue 3 推荐)
import {
onBeforeMount, onMounted,
onBeforeUpdate, onUpdated,
onBeforeUnmount, onUnmounted
} from 'vue'
// setup 本身就是 created/beforeCreate 阶段
setup() {
onBeforeMount(() => { })
onMounted(() => { })
onBeforeUpdate(() => { })
onUpdated(() => { })
onBeforeUnmount(() => { })
onUnmounted(() => { })
}
二、各钩子详解
2.1 setup(替代 beforeCreate / created)
vue
<script setup>
// setup 就是 created/beforeCreate 阶段
// 此时:props 已解析,DOM 未创建
import { ref } from 'vue'
const count = ref(0) // ✅ 可以访问 props
// document.getElementById('app') // ❌ DOM 未创建
</script>
2.2 onBeforeMount
javascript
import { onBeforeMount } from 'vue'
onBeforeMount(() => {
// DOM 挂载前调用
// 此时:虚拟 DOM 已创建,真实 DOM 未挂载
// 不能进行 DOM 操作
console.log('beforeMount')
})
2.3 onMounted
javascript
import { ref, onMounted } from 'vue'
const el = ref(null)
onMounted(() => {
// DOM 挂载后调用
// 此时:真实 DOM 已挂载,可以进行 DOM 操作
console.log('mounted')
// 获取 DOM 元素
console.log(el.value) // DOM 元素
// 初始化第三方库
const chart = echarts.init(el.value)
// 异步数据请求
fetchData()
})
2.4 onBeforeUpdate / onUpdated
javascript
import { ref, onBeforeUpdate, onUpdated } from 'vue'
const count = ref(0)
onBeforeUpdate(() => {
// DOM 更新前调用
// 此时:响应式数据已变化,DOM 尚未更新
console.log('beforeUpdate', count.value)
})
onUpdated(() => {
// DOM 更新后调用
// 此时:DOM 已更新为最新状态
console.log('updated', count.value)
// 若需基于最新 DOM 做测量,通常直接读即可
// 若在 onUpdated 内再次修改响应式数据,需 nextTick 等待下一轮 DOM 更新
})
2.5 onBeforeUnmount / onUnmounted
javascript
import { onBeforeUnmount, onUnmounted } from 'vue'
onBeforeUnmount(() => {
// 组件卸载前调用
// 此时:组件实例仍然可用
console.log('beforeUnmount')
})
onUnmounted(() => {
// 组件卸载后调用
// 此时:组件实例已销毁
console.log('unmounted')
// 清理定时器
clearInterval(timer)
// 取消事件监听
window.removeEventListener('resize', handler)
// 断开连接
websocket.close()
})
2.6 nextTick 与生命周期的关系
onMounted / onUpdated 触发时,Vue 已完成当前组件 的 DOM patch,但在以下场景仍可能需要 nextTick:
javascript
import { ref, onMounted, onUpdated, nextTick } from 'vue'
const list = ref([])
const count = ref(0)
onMounted(async () => {
list.value = await fetchList()
// 列表渲染完成后才能正确测量最后一项高度
await nextTick()
measureLastItem()
})
onUpdated(async () => {
// 在 updated 里改了数据,DOM 尚未反映这次变更
count.value++
await nextTick()
// 此时才能读到 count 变更后的 DOM
})
| 场景 | 推荐做法 |
|---|---|
| 组件首次挂载后操作 DOM | onMounted |
| 数据变更后读取/操作更新后的 DOM | nextTick(或在 onUpdated 中直接读) |
在 onUpdated 内再次改数据后读 DOM |
改完后 await nextTick() |
2.7 其他钩子(了解即可)
javascript
import { onServerPrefetch, onRenderTracked, onRenderTriggered } from 'vue'
// SSR:在服务端渲染前预取数据
onServerPrefetch(async () => {
await store.fetchUser()
})
// 开发调试:追踪响应式依赖何时触发渲染(仅 dev)
onRenderTracked((e) => console.log('依赖被追踪', e))
onRenderTriggered((e) => console.log('依赖触发更新', e))
日常业务开发以前面几节的钩子为主,以上三个按需查阅即可。
三、错误处理钩子
3.1 onErrorCaptured
vue
<script setup>
import { onErrorCaptured } from 'vue'
// 捕获子组件的错误
onErrorCaptured((err, instance, info) => {
console.error('捕获到错误:', err)
console.error('错误组件:', instance)
console.error('错误信息:', info)
// 返回 false 阻止错误继续向上传播
return false
})
</script>
3.2 应用场景
javascript
// 错误边界组件
import { ref, onErrorCaptured } from 'vue'
const error = ref(null)
onErrorCaptured((err) => {
error.value = err
// 阻止错误传播
return false
})
// 模板中展示降级 UI
// <div v-if="error">出错了: {{ error.message }}</div>
// <slot v-else />
四、keep-alive 特殊生命周期
4.1 onActivated / onDeactivated
vue
<script setup>
import { onActivated, onDeactivated } from 'vue'
// keep-alive 缓存的组件激活时调用
onActivated(() => {
console.log('activated')
// 重新获取数据或恢复状态
})
// keep-alive 缓存的组件停用时调用
onDeactivated(() => {
console.log('deactivated')
// 清理资源
})
</script>
4.2 与 onMounted / onUnmounted 的区别
javascript
// 普通组件(无 keep-alive)
// 挂载 → onMounted
// 卸载 → onUnmounted
// keep-alive 缓存的组件
// 首次挂载 → onMounted + onActivated(两者都会触发)
// 切换离开 → onDeactivated(不触发 onUnmounted)
// 切换回来 → onActivated(不触发 onMounted)
// 彻底销毁 → onUnmounted
vue
<!-- 父组件 -->
<template>
<KeepAlive>
<ChildA v-if="showA" />
<ChildB v-else />
</KeepAlive>
</template>
<!-- ChildA 的生命周期: -->
<!-- 首次渲染:onMounted + onActivated -->
<!-- 切换到 B:onDeactivated(不触发 onUnmounted) -->
<!-- 切换回来:onActivated(不触发 onMounted) -->
五、父子组件执行顺序
5.1 挂载顺序
父组件 setup
父组件 onBeforeMount
↓
子组件 setup
子组件 onBeforeMount
子组件 onMounted
↓
父组件 onMounted
vue
<!-- 父组件 -->
<script setup>
import { onBeforeMount, onMounted } from 'vue'
import Child from './Child.vue'
onBeforeMount(() => console.log('父 beforeMount'))
onMounted(() => console.log('父 mounted'))
</script>
<!-- 子组件 -->
<script setup>
import { onBeforeMount, onMounted } from 'vue'
onBeforeMount(() => console.log('子 beforeMount'))
onMounted(() => console.log('子 mounted'))
</script>
<!-- 输出顺序: -->
<!-- 父 beforeMount -->
<!-- 子 beforeMount -->
<!-- 子 mounted -->
<!-- 父 mounted -->
5.2 卸载顺序
父组件 onBeforeUnmount
↓
子组件 onBeforeUnmount
子组件 onUnmounted
↓
父组件 onUnmounted
javascript
// 子组件先卸载,父组件后卸载
// 子组件的清理逻辑在父组件之前执行
5.3 更新顺序
数据变化触发重渲染时,顺序与挂载类似:子组件先完成更新,父组件后完成。
父组件 onBeforeUpdate
↓
子组件 onBeforeUpdate
子组件 onUpdated
↓
父组件 onUpdated
javascript
// 挂载 / 更新:子组件「完成」在前,父组件「完成」在后
// 卸载:子组件「完成」在前,父组件「完成」在后
// 规律:before* 钩子父先子后,*ed / mounted / unmounted 钩子子先父后
六、常见应用场景
6.1 数据请求
数据请求不必须 写在 onMounted,按是否依赖 DOM、是否随路由变化来选:
javascript
import { ref, onMounted, watch } from 'vue'
import { useRoute } from 'vue-router'
const route = useRoute()
const data = ref(null)
const loading = ref(true)
// 方式一:setup 中直接请求(不依赖 DOM,尽早发起)
async function fetchData() {
loading.value = true
try {
const res = await fetch('/api/data')
data.value = await res.json()
} finally {
loading.value = false
}
}
fetchData()
// 方式二:onMounted(需要 DOM 就绪,或明确「挂载后再请求」)
onMounted(() => {
fetchData()
})
// 方式三:watch 路由参数(列表 → 详情等场景)
watch(
() => route.params.id,
(id) => {
if (id) fetchDetail(id)
},
{ immediate: true }
)
| 写法 | 适用场景 |
|---|---|
setup 顶层 / 普通函数 |
纯数据请求,不依赖 DOM |
onMounted |
依赖 DOM 尺寸/第三方库,或需等组件挂载后再请求 |
watch + immediate |
路由参数、props 变化时重新请求 |
6.2 事件监听与清理
javascript
import { onMounted, onUnmounted } from 'vue'
const handleResize = () => {
console.log('窗口大小变化')
}
onMounted(() => {
window.addEventListener('resize', handleResize)
})
onUnmounted(() => {
window.removeEventListener('resize', handleResize)
})
6.3 定时器管理
javascript
import { ref, onMounted, onUnmounted } from 'vue'
const count = ref(0)
let timer = null
onMounted(() => {
timer = setInterval(() => {
count.value++
}, 1000)
})
onUnmounted(() => {
clearInterval(timer) // 防止内存泄漏
})
6.4 第三方库初始化
javascript
import { ref, onMounted, onUnmounted } from 'vue'
const chartRef = ref(null)
let chart = null
const handleResize = () => chart?.resize()
onMounted(() => {
// 初始化 ECharts
chart = echarts.init(chartRef.value)
chart.setOption({ ... })
window.addEventListener('resize', handleResize)
})
onUnmounted(() => {
window.removeEventListener('resize', handleResize)
chart?.dispose()
})
6.5 watch 清理与 onUnmounted
Composition API 中,副作用清理不一定只靠 onUnmounted:
javascript
import { ref, watch, watchEffect, onUnmounted } from 'vue'
const count = ref(0)
// watch 返回 stop 函数,需手动停止时可调用
const stopWatch = watch(count, (val) => {
console.log(val)
})
// 组件内 watchEffect 会在组件卸载时自动停止
watchEffect(() => {
console.log(count.value)
})
onUnmounted(() => {
stopWatch() // 提前停止 watch,避免卸载前仍触发
})
组件内的
watch/watchEffect默认会随组件销毁而停止;跨组件或手动创建的 watcher 才需要显式stop()。
七、面试聚焦
7.1 为什么父组件 onMounted 在子组件之后?
父组件模板渲染时会创建子组件实例。挂载阶段遵循:
before*钩子:父 → 子(自上而下通知即将发生)mounted/updated/unmounted:子 → 父(自下而上确认已完成)
因此父组件 onMounted 执行时,子组件 DOM 已挂载完毕,可以在父组件里安全访问子组件 ref。
7.2 onUpdated 和 nextTick 有什么区别?
- onUpdated :每次响应式触发的 DOM 更新完成后自动调用,可能频繁触发
- nextTick :在当前 DOM 更新队列 flush 后执行一次回调,按需调用
javascript
count.value++ // 触发更新
await nextTick() // 等待本次更新对应的 DOM patch 完成
// 适合在函数/事件处理器中读取更新后的 DOM
onUpdated(() => { ... }) // 每次数据变化导致 DOM 更新后都会执行
不要在 onUpdated 里无条件修改响应式数据,容易形成更新死循环;若必须修改,加条件判断或改用 nextTick。
7.3 注册多个 onMounted 的执行顺序?
javascript
onMounted(() => console.log(1))
onMounted(() => console.log(2))
onMounted(() => console.log(3))
// 输出:1 → 2 → 3(按注册顺序依次执行)
同一钩子多次注册会全部执行,顺序与注册顺序一致。
7.4 async setup 会影响生命周期吗?
javascript
// setup 可以是 async,但组件会进入 Suspense 边界
export default {
async setup() {
const data = await fetchData()
onMounted(() => {
// 仍然有效,在挂载后执行
})
return { data }
}
}
async setup 不会阻止 onMounted 等钩子注册;组件在 setup Promise resolve 前不会渲染,需配合 <Suspense> 处理加载态。
八、易混淆点
- setup 中的同步代码等价于 created :Composition API 无需再写
created;Options API 中beforeCreate/created仍可用。 - onMounted ≠ DOM 一定「全部就绪」 :当前组件 DOM 已挂载,但若刚改了列表数据,子节点可能尚未渲染,需
await nextTick()后再测量。 - onUpdated 会多次触发 :不要在回调里无条件改响应式数据;读 DOM 优先用
nextTick,或确保有终止条件。 - keep-alive 首次挂载 :
onMounted与onActivated同时触发;缓存切换不触发onUnmounted。 - before 与 ed 的顺序规律 :
beforeMount / beforeUpdate / beforeUnmount父先子后;mounted / updated / unmounted子先父后。 - 清理资源 :定时器、DOM 事件、
watch的stop()、第三方实例dispose()等,在onUnmounted(或onDeactivated)中处理。
九、思考与练习
1. 父组件 onMounted 里能否安全访问子组件 ref?为什么?
解析:可以。子组件 onMounted 先于父组件执行,父组件 onMounted 触发时子组件 DOM 已挂载。
2. 数据请求放在 setup 和 onMounted 各有什么适用场景?
解析:
- setup:不依赖 DOM,希望尽早发起请求
- onMounted:依赖 DOM 或第三方库初始化后再请求
3. keep-alive 缓存的组件会触发哪些生命周期?
解析:
- 首次挂载:
onMounted→onActivated - 切换离开:
onDeactivated - 切换回来:
onActivated - 彻底销毁:
onUnmounted
4. 响应式数据更新时,父子组件 onBeforeUpdate / onUpdated 的执行顺序?
解析:
- onBeforeUpdate:父 → 子
- onUpdated:子 → 父
5. 在事件处理函数里修改数据后立刻读取 DOM,应该用什么?
解析:使用 await nextTick() 等待本次更新 flush 后再读 DOM;onUpdated 适合监听「每次更新完成后」的副作用,不适合在单次事件里替代 nextTick。
6. 如何避免内存泄漏?
解析:在 onUnmounted(keep-alive 场景还应在 onDeactivated)中清理资源:
javascript
onUnmounted(() => {
clearInterval(timer)
window.removeEventListener('resize', handler)
stopWatch()
chart?.dispose()
})
总结
- setup:替代 beforeCreate / created(Composition API),props 已解析,DOM 未创建
- onMounted :当前组件 DOM 已挂载,适合第三方库初始化;列表类 DOM 操作可能还需
nextTick - onUpdated :每次 DOM 更新后触发,避免无条件改状态;与
nextTick配合读取最新 DOM - onUnmounted:清理定时器、事件监听、watch、第三方实例
- keep-alive :首次
onMounted+onActivated;缓存切换走onActivated/onDeactivated - 父子顺序 :
before*父先子后;mounted / updated / unmounted子先父后 - 数据请求 :不依赖 DOM 可放 setup;依赖 DOM 或挂载后再请求用
onMounted;随路由/props 变化用watch