第 7 章:生命周期
学习目标
- 掌握 8 个生命周期钩子的触发时机
- 在合适阶段发起请求 / 操作 DOM
- 区分
onMounted与onActivated - 做好清理工作避免内存泄漏
一、生命周期图谱
| 钩子 | 触发时机 |
|---|---|
onBeforeMount | 组件挂载前 |
onMounted | 挂载完成,DOM 已渲染 |
onBeforeUpdate | 响应式数据变化,DOM 更新前 |
onUpdated | DOM 更新完成 |
onBeforeUnmount | 卸载前 |
onUnmounted | 卸载完成 |
onActivated | <keep-alive> 激活 |
onDeactivated | <keep-alive> 失活 |
二、最常用的 onMounted / onUnmounted
vue
<script setup lang="ts">
import { ref, onMounted, onUnmounted } from 'vue'
const timer = ref<number | null>(null)
onMounted(() => {
timer.value = window.setInterval(() => {
console.log('tick')
}, 1000)
})
onUnmounted(() => {
if (timer.value) clearInterval(timer.value) // ⚠️ 必须清理
})
</script>⚠️ 坑 1:定时器、事件监听、
requestAnimationFrame必须在onUnmounted中清理,否则组件销毁后仍在运行,会泄漏。
三、数据请求放在哪里
vue
<script setup lang="ts">
import { ref, onMounted } from 'vue'
const list = ref<string[]>([])
onMounted(async () => {
const res = await fetch('/api/list')
list.value = await res.json()
})
</script>可以提取成组合式函数:
ts
// src/composables/useFetch.ts
import { ref, onMounted } from 'vue'
export function useFetch<T>(url: string) {
const data = ref<T | null>(null)
const loading = ref(false)
const error = ref<Error | null>(null)
onMounted(async () => {
loading.value = true
try {
data.value = await (await fetch(url)).json()
} catch (e) {
error.value = e as Error
} finally {
loading.value = false
}
})
return { data, loading, error }
}四、onUpdated 慎用
vue
<script setup lang="ts">
import { ref, onUpdated } from 'vue'
const count = ref(0)
onUpdated(() => {
console.log('DOM 已更新', count.value)
})
</script>
<template>
<button @click="count++">{{ count }}</button>
</template>⚠️ 坑 2:
onUpdated里不要改响应式数据,会触发再次更新,死循环。
五、keep-alive 激活
vue
<!-- 父组件 -->
<keep-alive>
<component :is="currentTab" />
</keep-alive>vue
<!-- TabA.vue -->
<script setup lang="ts">
import { onActivated, onDeactivated } from 'vue'
onActivated(() => console.log('A 激活'))
onDeactivated(() => console.log('A 失活'))
</script>⚠️ 关键:
<keep-alive>默认缓存所有子组件,这时defineOptions({ name: 'TabA' })不写也能跑;但一旦写:include或:exclude,就必须给每个被匹配的子组件显式命名,否则 include 匹配不到,TabA 被销毁、切回去重渲染——等于没 keep-alive。三种情况对照:
写法 需要 name 吗 效果 <keep-alive>(裸用)❌ 缓存所有切换过的组件 <keep-alive :include="['TabA']">✅ 必须 只缓存叫 TabA 的 <keep-alive :exclude="['TabC']">✅ 必须 缓存除 TabC 外的 <keep-alive :max="3">❌ 只限制缓存数量,不靠 name 就算不写 include 也建议加 name,理由:
- Vue DevTools 看组件树:没 name 显示
<Anonymous>,排查痛苦- 递归组件:组件自己调自己,没 name 直接报错
- 错误上报(Sentry 等):没 name 堆栈全是匿名组件,定位不到
- 后期改造:从"全缓存"改成"白名单缓存",提前写好不用返工
正确写法:
vue
<!-- TabA.vue -->
<script setup lang="ts">
import { onActivated, onDeactivated } from 'vue'
defineOptions({
name: 'TabA', // ① 必须:给 keep-alive 的 :include 匹配
})
onActivated(() => console.log('A 激活'))
onDeactivated(() => console.log('A 失活'))
</script>六、错误处理
vue
<script setup lang="ts">
import { onErrorCaptured } from 'vue'
onErrorCaptured((err) => {
console.error('子组件错误', err)
return false // 阻止向上冒泡
})
</script>💡
onErrorCaptured= "组件级 try-catch"——子组件渲染/方法报错时,父组件接住,可以打印上报 / 弹提示 / 不让 App 白屏。三个核心场景:
场景 不处理的痛 接住之后 第三方组件挂了 一处报错 → 整个 App 白屏 只挂那一片,App 照常 错误上报(Sentry) 看不到用户报的错 自动 Sentry.capture(err)统一弹错误提示 用户看到一个空白页 弹"加载失败,点击重试" 经典用法:ErrorBoundary 包裹第三方组件:
vue<!-- ErrorBoundary.vue:通用错误边界组件 --> <script setup lang="ts"> import { onErrorCaptured, ref } from 'vue' const hasError = ref(false) onErrorCaptured((err, instance, info) => { // err → 错误对象;instance → 出错的组件实例;info → 错误来源 console.error('[全局]', info, err) Sentry.captureException(err) // 上报监控 hasError.value = true return false // ❗关键:阻止冒泡到全局 errorHandler(防双重上报) }) </script> <template> <slot v-if="!hasError" /> <p v-else>加载失败,请刷新</p> </template>vue<!-- 父组件 --> <template> <ErrorBoundary> <ThirdPartyChart :data="chartData" /> <!-- 图表挂了,只这一片报错 --> </ErrorBoundary> <ErrorBoundary> <ThirdPartyMap :city="city" /> <!-- 地图挂了,只这一片报错 --> </ErrorBoundary> </template>
return false是关键:不写或return true→ 错误继续冒到 Vue 全局 errorHandler(可能双重上报);return false→ 错误到此为止,Vue 不再处理。三个钩子分工:
API 作用 onErrorCaptured组件级兜底(子组件报错时触发) errorHandler(全局)整个 App 最后兜底,通常只接上报(在 main.ts注册)warnHandler(全局)Vue 警告兜底 啥时候用:
情况 做法 普通业务组件 不用,让它抛上去(全局 errorHandler 接) 第三方 / 不稳定组件 包一层 ErrorBoundary,崩了不影响主区错误上报 全局 errorHandler+ Sentry 自动接
七、调试小技巧
vue
<script setup lang="ts">
import { onMounted, onBeforeUnmount } from 'vue'
onMounted(() => console.log('mounted'))
onBeforeUnmount(() => console.log('beforeUnmount'))
</script>DevTools 装 Vue DevTools 扩展,可以看到组件树和生命周期调用。
八、本章小结
| 钩子 | 典型用途 |
|---|---|
onMounted | 发起请求、初始化第三方库 |
onUnmounted | 清理定时器、监听 |
onUpdated | 依赖 DOM 更新后做的事(慎用) |
onActivated | <keep-alive> 激活时恢复 |
onErrorCaptured | 捕获后代组件错误 |
动手练习
- 时钟:挂载时
setInterval刷新时间,卸载时清理 - 滚动监听:监听
window.scroll,组件卸载时removeEventListener - 切换缓存:用
<keep-alive>包裹列表,看激活/失活日志
下一章:第 8 章:插槽 →