Vue 生命周期
每个 Vue 组件实例在创建时都会经历一系列初始化步骤:数据观测、模板编译、挂载到 DOM,以及数据更新时重新渲染。在这个过程中,Vue 会调用对应的生命周期钩子函数,让开发者可以在特定阶段执行自己的逻辑。
生命周期图示
生命周期钩子
beforeCreate
在实例初始化之后、data 和 methods 设置之前被调用。此时:
- 组件实例刚创建
data、methods不可用$refs为空对象
<script setup>
import { ref } from 'vue'
const count = ref(0) // count 是 undefined
console.log('beforeCreate:', count.value) // undefined
// setup() 相当于 beforeCreate + created
</script>
在使用 <script setup> 时,组合式 API 的 setup() 函数会在 beforeCreate 之前被调用。
created
在实例创建完成后被调用。此时:
data和methods已经设置完成$refs可用- 但 DOM 还未挂载,不能访问
$el
<script setup>
import { ref, onMounted } from 'vue'
const count = ref(0)
console.log('created:', count.value) // 0
// 常用于:初始化数据、调用 API、设置监听器
onMounted(() => {
console.log('mounted:', count.value) // DOM 已挂载
})
</script>
beforeMount
在模板编译完成、挂载到 DOM 之前被调用。此时:
- 模板已经编译成渲染函数
- DOM 还未真正挂载
<script setup>
import { ref } from 'vue'
const message = ref('Hello')
// 很少使用
</script>
mounted
在实例挂载到 DOM 后被调用。此时:
$el、$refs可用- 可以访问到真实的 DOM 元素
- 常用于:初始化第三方库、操作 DOM
<script setup>
import { ref, onMounted } from 'vue'
import * as echarts from 'echarts'
const chartRef = ref(null)
onMounted(() => {
// DOM 已挂载,可以操作
const chart = echarts.init(chartRef.value)
chart.setOption({ /* 配置 */ })
})
// 清理工作
import { onUnmounted } from 'vue'
onUnmounted(() => {
// 销毁图表实例
chart?.dispose()
})
</script>
<template>
<div ref="chartRef" style="width: 100%; height: 400px;"></div>
</template>
beforeUpdate
在响应式数据变化、DOM 重新渲染之前被调用。此时:
- 可以获取更新前的状态
- 适合在 DOM 更新前做额外处理
<script setup>
import { ref, onBeforeUpdate } from 'vue'
const count = ref(0)
onBeforeUpdate(() => {
console.log('更新前:', count.value)
})
</script>
updated
在 DOM 重新渲染完成后被调用。此时:
- DOM 已经更新
- 避免在 updated 中修改状态,可能导致无限循环
<script setup>
import { ref, onUpdated } from 'vue'
const count = ref(0)
onUpdated(() => {
console.log('更新后: DOM 已更新')
})
</script>
beforeUnmount
在组件实例卸载之前被调用。此时:
- 组件实例仍然完全可用
- 常用于:清理定时器、取消订阅、移除事件监听
<script setup>
import { ref, onBeforeUnmount } from 'vue'
const timer = setInterval(() => {
console.log('interval')
}, 1000)
onBeforeUnmount(() => {
clearInterval(timer) // 清理定时器
})
</script>
unmounted
在组件实例卸载完成后被调用。此时:
- 所有子组件已卸载
- 所有指令已解除绑定
- 事件监听已移除
- 常用于:清理组件相关的资源
<script setup>
import { ref, onUnmounted } from 'vue'
onUnmounted(() => {
console.log('组件已卸载')
})
</script>
组合式 API 中的生命周期钩子
在 <script setup> 中使用组合式 API 的生命周期钩子:
import {
onMounted,
onUpdated,
onUnmounted,
onBeforeMount,
onBeforeUpdate,
onBeforeUnmount,
onErrorCaptured,
onRenderTracked,
onRenderTriggered
} from 'vue'
| 选项式 API | 组合式 API |
|---|---|
| beforeCreate | 不需要(setup 替代) |
| created | 不需要(setup 替代) |
| beforeMount | onBeforeMount |
| mounted | onMounted |
| beforeUpdate | onBeforeUpdate |
| updated | onUpdated |
| beforeUnmount | onBeforeUnmount |
| unmounted | onUnmounted |
| errorCaptured | onErrorCaptured |
| renderTracked | onRenderTracked |
| renderTriggered | onRenderTriggered |
实际应用示例
示例:数据获取
<script setup>
import { ref, onMounted } from 'vue'
const users = ref([])
const loading = ref(true)
const error = ref(null)
async function fetchUsers() {
try {
loading.value = true
const response = await fetch('/api/users')
users.value = await response.json()
} catch (e) {
error.value = e.message
} finally {
loading.value = false
}
}
onMounted(() => {
fetchUsers()
})
</script>
示例:定时器管理
<script setup>
import { ref, onMounted, onUnmounted } from 'vue'
const time = ref(new Date())
let timer = null
onMounted(() => {
timer = setInterval(() => {
time.value = new Date()
}, 1000)
})
onUnmounted(() => {
if (timer) {
clearInterval(timer)
}
})
</script>
示例:窗口监听器
<script setup>
import { ref, onMounted, onUnmounted } from 'vue'
const windowWidth = ref(window.innerWidth)
function handleResize() {
windowWidth.value = window.innerWidth
}
onMounted(() => {
window.addEventListener('resize', handleResize)
})
onUnmounted(() => {
window.removeEventListener('resize', handleResize)
})
</script>
示例:错误处理
<script setup>
import { ref, onErrorCaptured } from 'vue'
const error = ref(null)
onErrorCaptured((err, instance, info) => {
error.value = err.message
console.error('错误信息:', err)
console.error('错误组件:', instance)
console.error('错误详情:', info)
return false // 阻止错误继续传播
})
</script>
KeepAlive 相关钩子
当使用 <KeepAlive> 缓存组件时,组件实例会被保留而不是销毁。Vue 提供了两个专门的生命周期钩子来处理这种情况。
onActivated
当组件被 <KeepAlive> 缓存后再次激活时调用:
<script setup>
import { ref, onActivated } from 'vue'
const lastActivated = ref(null)
onActivated(() => {
lastActivated.value = new Date().toLocaleTimeString()
console.log('组件被激活')
})
</script>
<template>
<div>
<p>上次激活时间: {{ lastActivated }}</p>
</div>
</template>
使用场景:
- 刷新缓存的数据
- 重置滚动位置
- 重新启动定时器或动画
onDeactivated
当组件被 <KeepAlive> 缓存并停用时调用:
<script setup>
import { onDeactivated } from 'vue'
onDeactivated(() => {
console.log('组件被停用(缓存)')
// 可以暂停一些不需要的后台任务
})
</script>
完整示例:KeepAlive 组件
<!-- TabPanel.vue -->
<script setup>
import { ref, onActivated, onDeactivated } from 'vue'
const props = defineProps(['name'])
const visitCount = ref(0)
onActivated(() => {
visitCount.value++
console.log(`${props.name} 面板激活,访问次数: ${visitCount.value}`)
})
onDeactivated(() => {
console.log(`${props.name} 面板停用`)
})
</script>
<template>
<div class="tab-panel">
<h3>{{ name }}</h3>
<p>访问次数: {{ visitCount }}</p>
</div>
</template>
<!-- App.vue -->
<script setup>
import { ref } from 'vue'
import TabPanel from './TabPanel.vue'
const currentTab = ref('home')
const tabs = ['home', 'profile', 'settings']
</script>
<template>
<div>
<button
v-for="tab in tabs"
:key="tab"
@click="currentTab = tab"
:class="{ active: currentTab === tab }"
>
{{ tab }}
</button>
<KeepAlive>
<TabPanel :name="currentTab" />
</KeepAlive>
</div>
</template>
关键区别:
onMounted:组件首次创建时调用一次onActivated:每次从缓存激活时都调用
服务端渲染钩子
onServerPrefetch
onServerPrefetch 是专门用于服务端渲染(SSR)的钩子,只在服务器端执行:
<script setup>
import { ref, onServerPrefetch, onMounted } from 'vue'
const data = ref(null)
// 只在服务端执行
onServerPrefetch(async () => {
// 在服务端预取数据,比客户端获取更快
data.value = await fetchOnServer()
})
// 在客户端执行
onMounted(async () => {
// 如果数据为空,说明组件是在客户端动态渲染的
if (!data.value) {
data.value = await fetchOnClient()
}
})
</script>
使用场景:
- 服务端数据预取,提升首屏加载速度
- SEO 优化,确保爬虫能获取完整内容
调试钩子
Vue 提供了两个调试专用的生命周期钩子,仅在开发模式下生效:
onRenderTracked
当组件渲染过程中追踪到响应式依赖时调用:
<script setup>
import { ref, onRenderTracked } from 'vue'
const count = ref(0)
onRenderTracked((event) => {
console.log('追踪到依赖:', event)
// event 包含: effect, target, type, key
debugger // 可以在调试器中检查
})
</script>
onRenderTriggered
当响应式依赖变化触发组件重新渲染时调用:
<script setup>
import { ref, onRenderTriggered } from 'vue'
const count = ref(0)
onRenderTriggered((event) => {
console.log('渲染被触发:', event)
// event 包含: effect, target, type, key, newValue, oldValue
debugger
})
</script>
调试事件对象:
type DebuggerEvent = {
effect: ReactiveEffect // 触发的副作用
target: object // 响应式对象
type: TrackOpTypes | TriggerOpTypes // 操作类型
key: any // 属性键
newValue?: any // 新值(仅 trigger)
oldValue?: any // 旧值(仅 trigger)
}
使用限制与注意事项
必须同步调用
生命周期钩子必须在 setup() 阶段同步调用:
<script setup>
import { onMounted } from 'vue'
// ✅ 正确:同步调用
onMounted(() => {
console.log('组件已挂载')
})
// ❌ 错误:异步调用
setTimeout(() => {
onMounted(() => {
// 这不会工作!
// 此时组件实例已经无法关联
})
}, 100)
// ❌ 错误:在 Promise 中调用
fetch('/api/data').then(() => {
onMounted(() => {
// 这不会工作!
})
})
</script>
外部函数调用
钩子可以在外部函数中调用,只要调用栈是同步的:
// composables/useTimer.js
import { ref, onMounted, onUnmounted } from 'vue'
export function useTimer(callback, delay) {
const timer = ref(null)
// ✅ 正确:在 composable 中注册钩子
// 调用栈是同步的,从 setup 中发起
onMounted(() => {
timer.value = setInterval(callback, delay)
})
onUnmounted(() => {
clearInterval(timer.value)
})
return { timer }
}
<script setup>
import { useTimer } from './composables/useTimer'
// 在 setup 中同步调用 composable
useTimer(() => console.log('tick'), 1000)
</script>
SSR 注意事项
在服务端渲染时,大部分生命周期钩子不会被调用:
| 钩子 | SSR 是否调用 |
|---|---|
| onBeforeMount | 否 |
| onMounted | 否 |
| onBeforeUpdate | 否 |
| onUpdated | 否 |
| onBeforeUnmount | 否 |
| onUnmounted | 否 |
| onActivated | 否 |
| onDeactivated | 否 |
| onServerPrefetch | 是 |
| onRenderTracked | 否 |
| onRenderTriggered | 否 |
编写 SSR 友好的代码:
<script setup>
import { onMounted } from 'vue'
// ❌ 错误:直接访问浏览器 API
const width = window.innerWidth
// ✅ 正确:在 onMounted 中访问浏览器 API
const width = ref(0)
onMounted(() => {
width.value = window.innerWidth
})
// ✅ 或者检查环境
if (typeof window !== 'undefined') {
// 浏览器环境
}
</script>
父子组件生命周期顺序
创建时:父 beforeCreate → 父 created → 父 beforeMount → 子 beforeCreate → 子 created → 子 beforeMount → 子 mounted → 父 mounted
卸载时:父 beforeUnmount → 子 beforeUnmount → 子 unmounted → 父 unmounted
更新时:父 beforeUpdate → 子 beforeUpdate → 子 updated → 父 updated
生命周期钩子速查表
| 组合式 API | 选项式 API | 调用时机 | 典型用途 |
|---|---|---|---|
| - | beforeCreate | 实例初始化后 | 几乎不用 |
| - | created | 实例创建完成 | 初始化数据 |
| onBeforeMount | beforeMount | 挂载前 | 很少使用 |
| onMounted | mounted | 挂载后 | 操作 DOM、第三方库 |
| onBeforeUpdate | beforeUpdate | 更新前 | 获取更新前 DOM |
| onUpdated | updated | 更新后 | 访问更新后 DOM |
| onBeforeUnmount | beforeUnmount | 卸载前 | 清理准备 |
| onUnmounted | unmounted | 卸载后 | 清理副作用 |
| onActivated | activated | KeepAlive 激活 | 刷新数据 |
| onDeactivated | deactivated | KeepAlive 停用 | 暂停任务 |
| onServerPrefetch | - | SSR 渲染前 | 服务端数据预取 |
| onErrorCaptured | errorCaptured | 捕获错误 | 错误处理 |
| onRenderTracked | - | 追踪依赖 | 调试 |
| onRenderTriggered | - | 触发渲染 | 调试 |
小结
本章我们详细学习了 Vue 生命周期的完整内容:
- 生命周期图示:了解各阶段的执行顺序
- 各阶段钩子函数:beforeCreate 到 unmounted
- 组合式 API 钩子:onMounted、onUnmounted 等
- 实际应用:数据获取、定时器、事件监听、错误处理
- 父子组件顺序:创建和卸载时的执行顺序
练习
- 创建一个自动刷新数据的组件
- 创建一个带有清理功能的组件
- 实现一个错误边界组件
准备好进入下一章,学习 ref 和 reactive 的内容了吗?