【Vue 3 系列·第四篇】生命周期钩子完全指南:执行时机、Vue 2 对比、Composable 中的正确用法
·
【Vue 3 系列·第四篇】生命周期钩子完全指南:执行时机、Vue 2 对比、Composable 中的正确用法
更新时间:2026-05-19 | 阅读时长:约 20 分钟
系列:Vue 3 完全指南(共 8 篇)
标签:Vue3生命周期onMountedonUnmountedonBeforeUpdateonUpdatedComposableVue2对比

系列进度
| 篇次 | 主题 | 状态 |
|---|---|---|
| 第一篇 | Vue 3 是什么:Composition API vs Options API | ✅ 已发布 |
| 第二篇 | 响应式系统:ref、reactive、computed、watch | ✅ 已发布 |
| 第三篇 | 组件通信:props、emit、provide/inject | ✅ 已发布 |
| 第四篇(本篇) | 生命周期钩子:执行时机与正确用法 | — |
| 第五篇 | 路由:Vue Router 4 实战 | 即将发布 |
| 第六篇 | 状态管理:Pinia 完全指南 | 即将发布 |
| 第七篇 | 性能优化:懒加载、keep-alive、v-memo | 即将发布 |
| 第八篇 | 工程化:Vite + TypeScript + 最佳实践 | 即将发布 |
目录
- 一、生命周期全景图
- 二、Vue 2 vs Vue 3 生命周期对比
- 三、创建阶段:setup 就是新的 beforeCreate + created
- 四、挂载阶段:onBeforeMount 与 onMounted
- 五、更新阶段:onBeforeUpdate 与 onUpdated
- 六、卸载阶段:onBeforeUnmount 与 onUnmounted
- 七、特殊钩子:keep-alive、错误处理、服务端渲染
- 八、在 Composable 中使用生命周期
- 九、完整实战:数据大屏组件
一、生命周期全景图
组件生命周期(Vue 3 Composition API):
┌─────────────────────────────────────────────┐
│ createApp() / 父组件渲染到该组件 │
└──────────────────┬──────────────────────────┘
│
▼
┌─────────────────────────────────────────────┐
│ setup() │
│ ← beforeCreate + created 的替代 │
│ 执行响应式数据初始化、computed、watch 等 │
└──────────────────┬──────────────────────────┘
│
▼
┌─────────────────────────────────────────────┐
│ onBeforeMount() │
│ ← 模板编译完成,DOM 尚未插入 │
│ 此时 $el 还不存在 │
└──────────────────┬──────────────────────────┘
│
▼ DOM 渲染
┌─────────────────────────────────────────────┐
│ onMounted() ← 最常用 ★ │
│ DOM 已插入,可以访问 DOM、初始化第三方库 │
│ 可以发起数据请求 │
└──────────────────┬──────────────────────────┘
│
│ 数据变化触发重渲染(循环)
▼
┌─────────────────────────────────────────────┐
│ onBeforeUpdate() │
│ ← 数据已变,DOM 尚未更新 │
│ 可以在此访问更新前的 DOM 状态 │
└──────────────────┬──────────────────────────┘
│
▼ DOM 更新
┌─────────────────────────────────────────────┐
│ onUpdated() │
│ DOM 已更新,可以访问最新 DOM │
│ ⚠️ 避免在此修改状态(会导致无限循环) │
└──────────────────┬──────────────────────────┘
│
│ 组件被移除
▼
┌─────────────────────────────────────────────┐
│ onBeforeUnmount() │
│ ← 组件仍然完好,即将销毁 │
│ 最后一次访问 DOM 的机会 │
└──────────────────┬──────────────────────────┘
│
▼ 清理
┌─────────────────────────────────────────────┐
│ onUnmounted() ← 最常用 ★ │
│ 组件已销毁 │
│ 清理定时器、取消订阅、移除事件监听 │
└─────────────────────────────────────────────┘
二、Vue 2 vs Vue 3 生命周期对比
| Vue 2 选项 | Vue 3 钩子函数 | 说明 |
|---|---|---|
beforeCreate |
❌ 无对应(用 setup() 替代) |
setup 在其之前执行 |
created |
❌ 无对应(用 setup() 替代) |
setup 包含了这两者 |
beforeMount |
onBeforeMount |
挂载前 |
mounted |
onMounted |
挂载完成 |
beforeUpdate |
onBeforeUpdate |
更新前 |
updated |
onUpdated |
更新完成 |
beforeDestroy |
onBeforeUnmount |
卸载前(改名了!) |
destroyed |
onUnmounted |
卸载完成(改名了!) |
activated |
onActivated |
keep-alive 激活 |
deactivated |
onDeactivated |
keep-alive 停用 |
errorCaptured |
onErrorCaptured |
捕获子组件错误 |
| — | onRenderTracked |
调试:追踪响应式依赖(开发环境) |
| — | onRenderTriggered |
调试:触发重渲染(开发环境) |
| — | onServerPrefetch |
SSR 专用 |
关键变化记忆:
beforeDestroy → onBeforeUnmount (destroy 改为 unmount)
destroyed → onUnmounted (destroy 改为 unmount)
beforeCreate + created → setup() (合并了!)
三、创建阶段:setup 就是新的 beforeCreate + created
<script setup lang="ts">
import { ref, computed, watch } from 'vue'
// setup() 在组件实例创建时同步执行
// 等价于 Vue 2 的 beforeCreate + created
// ✅ 可以做:
// 初始化响应式数据
const count = ref(0)
const name = ref('Vue')
// 定义计算属性
const doubled = computed(() => count.value * 2)
// 注册侦听器
watch(count, (val) => console.log('count changed:', val))
// 同步的初始化逻辑
const initialData = processData()
// ⚠️ 不能做:
// 访问 DOM(DOM 还没有渲染)
// document.querySelector('.my-el') // ❌ 元素不存在
// 访问子组件实例
// childRef.value?.someMethod() // ❌ 子组件还没挂载
// 注意:setup 中的 console.log 会在组件渲染前执行
console.log('setup 执行,此时 DOM 还不存在')
function processData() {
return { items: [], total: 0 }
}
</script>
3.1 在 setup 中发起异步请求
<script setup lang="ts">
import { ref, onMounted } from 'vue'
const users = ref([])
const loading = ref(false)
// ❌ 不推荐:直接在 setup 顶层 await
// 这需要组件使用 <Suspense> 包裹,且行为特殊
// const data = await fetchUsers()
// ✅ 推荐:在 onMounted 中发起请求(或使用 watch)
onMounted(async () => {
loading.value = true
try {
users.value = await fetchUsers()
} finally {
loading.value = false
}
})
// ✅ 也可以:定义异步函数,在 setup 中同步调用(不 await)
async function loadUsers() {
loading.value = true
try {
users.value = await fetchUsers()
} finally {
loading.value = false
}
}
// 立即执行(不阻塞 setup)
loadUsers()
async function fetchUsers() {
const res = await fetch('/api/users')
return res.json()
}
</script>
四、挂载阶段:onBeforeMount 与 onMounted
4.1 onBeforeMount
<script setup lang="ts">
import { onBeforeMount } from 'vue'
onBeforeMount(() => {
// 此时:
// ✅ 响应式数据已准备好
// ✅ 模板已编译成渲染函数
// ❌ DOM 还未插入页面
// ❌ $el 还不存在
console.log('onBeforeMount:即将挂载到 DOM')
// 使用场景:非常少见
// 可以做最后一次数据初始化(但通常在 setup 就做了)
})
</script>
4.2 onMounted(最常用)
<script setup lang="ts">
import { ref, onMounted, useTemplateRef } from 'vue'
const chartRef = useTemplateRef<HTMLDivElement>('chartRef')
const inputRef = useTemplateRef<HTMLInputElement>('inputRef')
onMounted(() => {
// 此时:
// ✅ DOM 已插入页面
// ✅ 可以访问 DOM 元素
// ✅ 子组件也已挂载完成
// ✅ 可以初始化需要 DOM 的第三方库
console.log('onMounted:DOM 已就绪')
console.log(chartRef.value) // HTMLDivElement
// 常见用途1:初始化 ECharts / D3.js 等图表库
const chart = echarts.init(chartRef.value!)
chart.setOption({ /* ... */ })
// 常见用途2:自动聚焦
inputRef.value?.focus()
// 常见用途3:设置事件监听(记得在 onUnmounted 中移除!)
window.addEventListener('resize', handleResize)
// 常见用途4:发起数据请求
fetchInitialData()
})
function handleResize() { /* ... */ }
async function fetchInitialData() { /* ... */ }
</script>
<template>
<div ref="chartRef" class="chart-container" />
<input ref="inputRef" />
</template>
4.3 onMounted 的执行顺序(父子组件)
父组件 onBeforeMount
子组件 onBeforeMount
子组件 onMounted ← 子先挂载完
父组件 onMounted ← 父后挂载完
规律:子组件先 mounted,父组件后 mounted
这意味着:父组件的 onMounted 中可以安全访问子组件实例
<!-- 父组件 -->
<script setup>
import { onMounted, useTemplateRef } from 'vue'
import ChildComponent from './ChildComponent.vue'
const childRef = useTemplateRef('childRef')
onMounted(() => {
// ✅ 此时子组件已经挂载完成,可以调用其方法
childRef.value?.initialize()
})
</script>
<template>
<ChildComponent ref="childRef" />
</template>
五、更新阶段:onBeforeUpdate 与 onUpdated
<script setup lang="ts">
import { ref, onBeforeUpdate, onUpdated, useTemplateRef } from 'vue'
const count = ref(0)
const listRef = useTemplateRef<HTMLUListElement>('listRef')
onBeforeUpdate(() => {
// 数据已变,DOM 还未更新
// 可以访问更新前的 DOM 状态
console.log('DOM 更新前,列表高度:', listRef.value?.offsetHeight)
// 使用场景:保存更新前的 DOM 状态(如滚动位置)
})
onUpdated(() => {
// DOM 已更新,可以访问最新 DOM
console.log('DOM 已更新,列表高度:', listRef.value?.offsetHeight)
// ⚠️ 重要警告:不要在 onUpdated 中修改响应式数据!
// count.value++ // ❌ 会导致无限更新循环!
// ✅ 如果需要在更新后做某事,用条件限制
// if (someCondition) { ... } // 确保不会每次更新都触发修改
// ✅ 正确使用:读取更新后的 DOM 信息
// 比如滚动到新增的消息(聊天应用)
})
</script>
<template>
<button @click="count++">+1 ({{ count }})</button>
<ul ref="listRef">
<li v-for="i in count" :key="i">Item {{ i }}</li>
</ul>
</template>
5.1 onUpdated vs watch + nextTick
<script setup lang="ts">
import { ref, watch, nextTick } from 'vue'
const message = ref('')
// 方式1:watch + nextTick(更精确,只在指定数据变化时执行)
watch(message, async () => {
await nextTick() // 等待 DOM 更新完成
// 此时 DOM 已更新,与 onUpdated 类似
console.log('message 变化后的 DOM:', document.getElementById('msg')?.textContent)
})
// 方式2:onUpdated(任意数据变化都触发,粒度粗)
// 通常 watch + nextTick 比 onUpdated 更精确
</script>
六、卸载阶段:onBeforeUnmount 与 onUnmounted
<script setup lang="ts">
import { ref, onMounted, onBeforeUnmount, onUnmounted } from 'vue'
const timer = ref<ReturnType<typeof setInterval>>()
const wsClient = ref<WebSocket>()
const observer = ref<IntersectionObserver>()
onMounted(() => {
// 设置各种需要清理的资源
// 1. 定时器
timer.value = setInterval(() => {
console.log('timer tick')
}, 1000)
// 2. WebSocket
wsClient.value = new WebSocket('ws://example.com')
wsClient.value.onmessage = (e) => console.log(e.data)
// 3. 观察器
observer.value = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) loadMoreData()
})
})
observer.value.observe(document.querySelector('.sentinel')!)
// 4. 全局事件监听
window.addEventListener('resize', handleResize)
document.addEventListener('keydown', handleKeydown)
})
onBeforeUnmount(() => {
// 组件即将销毁,DOM 还在
// 最后一次读取 DOM 信息的机会
console.log('即将销毁,最后的 DOM 访问')
})
onUnmounted(() => {
// 组件已销毁,在这里清理所有副作用
// 1. 清除定时器
if (timer.value) clearInterval(timer.value)
// 2. 关闭 WebSocket
wsClient.value?.close()
// 3. 断开观察器
observer.value?.disconnect()
// 4. 移除全局事件监听
window.removeEventListener('resize', handleResize)
document.removeEventListener('keydown', handleKeydown)
console.log('已清理所有副作用')
})
function handleResize() { /* ... */ }
function handleKeydown(e: KeyboardEvent) { /* ... */ }
function loadMoreData() { /* ... */ }
</script>
6.1 自动清理(推荐模式)
<script setup lang="ts">
import { onMounted, onUnmounted } from 'vue'
// 把"注册 + 清理"封装在一起,不容易遗漏
function useEventListener(
target: EventTarget,
event: string,
handler: EventListener,
) {
onMounted(() => target.addEventListener(event, handler))
onUnmounted(() => target.removeEventListener(event, handler))
}
// 使用:不需要手动在 onUnmounted 里清理
useEventListener(window, 'resize', () => console.log('resized'))
useEventListener(document, 'keydown', (e) => console.log(e))
// watchEffect 也会自动在组件卸载时停止
// watch 也是(在 setup 顶层创建时)
</script>
七、特殊钩子
7.1 keep-alive 相关:onActivated / onDeactivated
<!-- 父组件:使用 keep-alive 缓存组件 -->
<template>
<keep-alive>
<component :is="currentView" />
</keep-alive>
</template>
<!-- 被缓存的子组件 -->
<script setup lang="ts">
import { onMounted, onUnmounted, onActivated, onDeactivated } from 'vue'
onMounted(() => {
// 第一次挂载时执行(只执行一次)
console.log('首次挂载')
})
onUnmounted(() => {
// keep-alive 包裹时:不会执行!(组件被缓存,不是销毁)
// 只有手动销毁 keep-alive 时才执行
console.log('真正销毁')
})
onActivated(() => {
// 每次组件从缓存中激活时执行(包括第一次)
console.log('激活(从缓存恢复)')
// 使用场景:刷新数据(如页面从 tab 切换回来)
refreshData()
})
onDeactivated(() => {
// 每次组件被缓存(不是销毁)时执行
console.log('停用(进入缓存)')
// 使用场景:暂停视频、停止动画等
pauseVideo()
})
function refreshData() { /* 重新拉取数据 */ }
function pauseVideo() { /* 暂停播放 */ }
</script>
7.2 错误捕获:onErrorCaptured
<script setup lang="ts">
import { onErrorCaptured, ref } from 'vue'
const hasError = ref(false)
const errorMessage = ref('')
// 捕获后代组件抛出的错误
onErrorCaptured((error, instance, info) => {
hasError.value = true
errorMessage.value = error.message
console.error('捕获到错误:', error)
console.error('来自组件:', instance)
console.error('错误信息:', info) // 如 'in mounted hook'
// 返回 false:阻止错误继续向上传播
return false
// 返回 true 或不返回:错误继续向上传播
})
</script>
<template>
<div v-if="hasError" class="error-boundary">
<p>出错了:{{ errorMessage }}</p>
<button @click="hasError = false">重试</button>
</div>
<slot v-else />
</template>
7.3 调试钩子(开发环境)
<script setup>
import { onRenderTracked, onRenderTriggered } from 'vue'
// 仅在开发环境有效,生产环境不执行
// 每次组件渲染时追踪哪些响应式依赖被访问
onRenderTracked((event) => {
console.log('依赖被追踪:', event)
// event.key:被访问的属性名
// event.type:操作类型(get/has/iterate)
})
// 每次组件重新渲染时,追踪是哪个依赖变化触发的
onRenderTriggered((event) => {
console.log('重渲染触发原因:', event)
// event.key:变化的属性名
// event.newValue:新值
// event.oldValue:旧值
})
</script>
八、在 Composable 中使用生命周期
这是 Composition API 的核心优势之一:生命周期钩子可以在 Composable 中使用,让逻辑更内聚。
8.1 基本模式
// composables/useWindowSize.ts
import { ref, onMounted, onUnmounted } from 'vue'
export function useWindowSize() {
const width = ref(window.innerWidth)
const height = ref(window.innerHeight)
function update() {
width.value = window.innerWidth
height.value = window.innerHeight
}
// 生命周期钩子在 Composable 中完全有效!
// 它们会绑定到调用该 Composable 的组件实例
onMounted(() => {
window.addEventListener('resize', update)
})
onUnmounted(() => {
window.removeEventListener('resize', update)
})
return { width, height }
}
<!-- 组件中使用 -->
<script setup>
import { useWindowSize } from '@/composables/useWindowSize'
// 自动处理事件监听的注册和清理,组件无需关心
const { width, height } = useWindowSize()
</script>
<template>
<p>窗口大小:{{ width }} × {{ height }}</p>
</template>
8.2 注意:Composable 必须在 setup 中同步调用
<script setup>
import { ref, onMounted } from 'vue'
import { useWindowSize } from '@/composables/useWindowSize'
// ✅ 在 setup 顶层同步调用
const { width } = useWindowSize()
// ❌ 不能在条件语句中调用
if (someCondition) {
const { width } = useWindowSize() // ❌ 生命周期钩子无法绑定到组件
}
// ❌ 不能在异步之后调用
onMounted(async () => {
await someAsyncOperation()
const { width } = useWindowSize() // ❌ 此时 setup 已执行完,无法绑定
})
// ✅ 如果需要条件逻辑,在 Composable 内部处理
</script>
8.3 常用 Composable 示例
// composables/useInterval.ts
// 封装定时器,自动在组件卸载时清理
import { onMounted, onUnmounted } from 'vue'
export function useInterval(fn: () => void, delay: number) {
let timer: ReturnType<typeof setInterval>
onMounted(() => {
timer = setInterval(fn, delay)
})
onUnmounted(() => {
clearInterval(timer)
})
}
// composables/useIntersectionObserver.ts
// 封装 IntersectionObserver,用于懒加载
import { ref, onMounted, onUnmounted } from 'vue'
import type { Ref } from 'vue'
export function useIntersectionObserver(
target: Ref<Element | null | undefined>,
callback: IntersectionObserverCallback,
options?: IntersectionObserverInit,
) {
const isIntersecting = ref(false)
let observer: IntersectionObserver
onMounted(() => {
if (!target.value) return
observer = new IntersectionObserver((entries, obs) => {
isIntersecting.value = entries[0]?.isIntersecting ?? false
callback(entries, obs)
}, options)
observer.observe(target.value)
})
onUnmounted(() => {
observer?.disconnect()
})
return { isIntersecting }
}
// composables/usePageVisibility.ts
// 页面可见性变化(如切换浏览器 Tab)
import { ref, onMounted, onUnmounted } from 'vue'
export function usePageVisibility() {
const isVisible = ref(!document.hidden)
function handleVisibilityChange() {
isVisible.value = !document.hidden
}
onMounted(() => {
document.addEventListener('visibilitychange', handleVisibilityChange)
})
onUnmounted(() => {
document.removeEventListener('visibilitychange', handleVisibilityChange)
})
return { isVisible }
}
九、完整实战:数据大屏组件
综合运用各生命周期钩子,实现一个实时数据大屏:
<!-- DataDashboard.vue -->
<script setup lang="ts">
import {
ref, reactive, computed,
onMounted, onBeforeUnmount, onUnmounted,
onActivated, onDeactivated,
onErrorCaptured,
} from 'vue'
import { useWindowSize } from '@/composables/useWindowSize'
import { useInterval } from '@/composables/useInterval'
// ── 窗口尺寸(Composable 自动处理监听和清理)──────────────
const { width, height } = useWindowSize()
// ── 响应式数据 ────────────────────────────────────────────
const metrics = reactive({
visitors: 0,
orders: 0,
revenue: 0,
errors: 0,
})
const chartInstance = ref<any>(null) // ECharts 实例
const wsConnection = ref<WebSocket>()
const isConnected = ref(false)
const hasError = ref(false)
const errorMsg = ref('')
const lastUpdateTime = ref<Date>()
// ── 计算属性 ──────────────────────────────────────────────
const conversionRate = computed(() =>
metrics.visitors > 0
? ((metrics.orders / metrics.visitors) * 100).toFixed(2) + '%'
: '0%'
)
// ── 生命周期 ──────────────────────────────────────────────
onMounted(() => {
console.log('[Dashboard] 挂载完成')
// 1. 初始化图表(需要 DOM)
initChart()
// 2. 建立 WebSocket 连接
connectWebSocket()
// 3. 初始加载数据
fetchInitialMetrics()
})
// keep-alive 激活时(如从其他 Tab 切换回来)
onActivated(() => {
console.log('[Dashboard] 激活,刷新数据')
// 重新连接 WebSocket(可能因为 Tab 切换断开了)
if (!isConnected.value) {
connectWebSocket()
}
fetchInitialMetrics() // 刷新数据
})
// keep-alive 停用时
onDeactivated(() => {
console.log('[Dashboard] 停用,暂停更新')
// 停用时断开 WebSocket 节省资源(激活时重连)
wsConnection.value?.close()
isConnected.value = false
})
// 卸载前:最后一次 DOM 访问
onBeforeUnmount(() => {
console.log('[Dashboard] 即将卸载')
// 保存当前状态到 localStorage(可选)
localStorage.setItem('dashboard-last-metrics', JSON.stringify(metrics))
})
// 卸载时:清理所有资源
onUnmounted(() => {
console.log('[Dashboard] 已卸载,清理资源')
// 1. 销毁图表实例(ECharts 需要手动销毁)
chartInstance.value?.dispose()
// 2. 关闭 WebSocket
wsConnection.value?.close()
})
// 捕获子组件错误
onErrorCaptured((error) => {
hasError.value = true
errorMsg.value = error.message
console.error('[Dashboard] 子组件错误:', error)
return false // 阻止继续向上传播
})
// ── 使用 Composable 的定时器(自动清理)────────────────────
// 每 30 秒刷新一次指标(keep-alive 停用时不执行,因为用了 onActivated 管理)
useInterval(() => {
if (isConnected.value) return // WebSocket 已连,不需要轮询
fetchInitialMetrics()
}, 30_000)
// ── 业务逻辑 ──────────────────────────────────────────────
function initChart() {
const el = document.getElementById('main-chart')
if (!el) return
// chartInstance.value = echarts.init(el)
// 更新图表选项...
console.log('图表初始化完成')
}
function connectWebSocket() {
try {
// wsConnection.value = new WebSocket('wss://api.example.com/realtime')
// wsConnection.value.onopen = () => { isConnected.value = true }
// wsConnection.value.onmessage = handleMessage
// wsConnection.value.onclose = () => { isConnected.value = false }
isConnected.value = true // 模拟
console.log('WebSocket 已连接')
} catch (e) {
console.error('WebSocket 连接失败', e)
}
}
function handleMessage(event: MessageEvent) {
const data = JSON.parse(event.data)
Object.assign(metrics, data)
lastUpdateTime.value = new Date()
updateChart()
}
function updateChart() {
if (!chartInstance.value) return
// chartInstance.value.setOption({ ... })
}
async function fetchInitialMetrics() {
try {
// const res = await fetch('/api/metrics')
// const data = await res.json()
// Object.assign(metrics, data)
// 模拟数据
Object.assign(metrics, {
visitors: Math.floor(Math.random() * 10000),
orders: Math.floor(Math.random() * 500),
revenue: Math.floor(Math.random() * 100000),
errors: Math.floor(Math.random() * 10),
})
lastUpdateTime.value = new Date()
} catch (e) {
console.error('获取指标失败', e)
}
}
</script>
<template>
<div class="dashboard" :style="{ width: width + 'px' }">
<div v-if="hasError" class="error-banner">
⚠️ 出现错误:{{ errorMsg }}
<button @click="hasError = false">×</button>
</div>
<header>
<h1>📊 实时数据大屏</h1>
<span :class="['status', isConnected ? 'online' : 'offline']">
{{ isConnected ? '● 实时' : '○ 离线' }}
</span>
<span v-if="lastUpdateTime" class="update-time">
更新于 {{ lastUpdateTime.toLocaleTimeString() }}
</span>
</header>
<div class="metrics-grid">
<div class="metric-card">
<h3>今日访客</h3>
<p class="number">{{ metrics.visitors.toLocaleString() }}</p>
</div>
<div class="metric-card">
<h3>今日订单</h3>
<p class="number">{{ metrics.orders.toLocaleString() }}</p>
</div>
<div class="metric-card">
<h3>今日收入</h3>
<p class="number">¥{{ metrics.revenue.toLocaleString() }}</p>
</div>
<div class="metric-card">
<h3>转化率</h3>
<p class="number">{{ conversionRate }}</p>
</div>
</div>
<div id="main-chart" style="height: 400px;" />
<button @click="fetchInitialMetrics">手动刷新</button>
</div>
</template>
<style scoped>
.dashboard { padding: 1.5rem; background: #0a0f1e; color: white; min-height: 100vh; }
.status.online { color: #42b883; }
.status.offline { color: #ff6b6b; }
.metrics-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 1rem; }
.metric-card { background: #1a2035; padding: 1rem; border-radius: 8px; }
.number { font-size: 2rem; font-weight: bold; color: #42b883; }
.error-banner { background: #ff6b6b; padding: 0.5rem 1rem; border-radius: 4px; }
</style>
总结
| 钩子 | 执行时机 | 主要用途 |
|---|---|---|
setup() |
实例创建时(同步) | 初始化数据、watch、computed |
onBeforeMount |
挂载前 | 极少使用 |
onMounted ⭐ |
DOM 就绪后 | 初始化第三方库、DOM 操作、发请求 |
onBeforeUpdate |
数据变、DOM 未更新 | 保存更新前 DOM 状态 |
onUpdated |
DOM 更新后 | 读取新 DOM(勿改数据!) |
onBeforeUnmount |
卸载前 | 最后一次 DOM 访问 |
onUnmounted ⭐ |
销毁后 | 清理定时器、关闭连接、移除监听 |
onActivated |
keep-alive 激活 | 刷新数据、重连 |
onDeactivated |
keep-alive 停用 | 暂停操作、节省资源 |
onErrorCaptured |
子组件报错 | 错误边界处理 |
核心原则:在 onMounted 里做初始化,在 onUnmounted 里做清理,把逻辑抽进 Composable 让代码更内聚。
下一篇预告:Vue Router 4 实战——路由配置、动态路由、路由守卫、useRoute 和 useRouter,以及如何实现权限控制。
💬 你有没有遇到过忘记在 onUnmounted 里清理定时器导致内存泄漏的问题? 欢迎评论区分享!
🙏 如果这篇帮到你,点赞 + 收藏,系列持续更新!
更多推荐
所有评论(0)