Skip to content
第 7 章 前端 ⏱ 12 分钟阅读

第 7 章:生命周期 ​

学习目标 ​

  • 掌握 8 个生命周期钩子的触发时机
  • 在合适阶段发起请求 / 操作 DOM
  • 区分 onMounted 与 onActivated
  • 做好清理工作避免内存泄漏

一、生命周期图谱 ​

钩子触发时机
onBeforeMount组件挂载前
onMounted挂载完成,DOM 已渲染
onBeforeUpdate响应式数据变化,DOM 更新前
onUpdatedDOM 更新完成
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,理由:

  1. Vue DevTools 看组件树:没 name 显示 <Anonymous>,排查痛苦
  2. 递归组件:组件自己调自己,没 name 直接报错
  3. 错误上报(Sentry 等):没 name 堆栈全是匿名组件,定位不到
  4. 后期改造:从"全缓存"改成"白名单缓存",提前写好不用返工

正确写法:

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捕获后代组件错误

动手练习 ​

  1. 时钟:挂载时 setInterval 刷新时间,卸载时清理
  2. 滚动监听:监听 window.scroll,组件卸载时 removeEventListener
  3. 切换缓存:用 <keep-alive> 包裹列表,看激活/失活日志

下一章:第 8 章:插槽 →

本站基于 VitePress 构建 · 由 StackHub 团队维护