跳到主要内容

Vue 生命周期

每个 Vue 组件实例在创建时都会经历一系列初始化步骤:数据观测、模板编译、挂载到 DOM,以及数据更新时重新渲染。在这个过程中,Vue 会调用对应的生命周期钩子函数,让开发者可以在特定阶段执行自己的逻辑。

生命周期图示

生命周期钩子

beforeCreate

在实例初始化之后、data 和 methods 设置之前被调用。此时:

  • 组件实例刚创建
  • datamethods 不可用
  • $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

在实例创建完成后被调用。此时:

  • datamethods 已经设置完成
  • $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 替代)
beforeMountonBeforeMount
mountedonMounted
beforeUpdateonBeforeUpdate
updatedonUpdated
beforeUnmountonBeforeUnmount
unmountedonUnmounted
errorCapturedonErrorCaptured
renderTrackedonRenderTracked
renderTriggeredonRenderTriggered

实际应用示例

示例:数据获取

<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实例创建完成初始化数据
onBeforeMountbeforeMount挂载前很少使用
onMountedmounted挂载后操作 DOM、第三方库
onBeforeUpdatebeforeUpdate更新前获取更新前 DOM
onUpdatedupdated更新后访问更新后 DOM
onBeforeUnmountbeforeUnmount卸载前清理准备
onUnmountedunmounted卸载后清理副作用
onActivatedactivatedKeepAlive 激活刷新数据
onDeactivateddeactivatedKeepAlive 停用暂停任务
onServerPrefetch-SSR 渲染前服务端数据预取
onErrorCapturederrorCaptured捕获错误错误处理
onRenderTracked-追踪依赖调试
onRenderTriggered-触发渲染调试

小结

本章我们详细学习了 Vue 生命周期的完整内容:

  1. 生命周期图示:了解各阶段的执行顺序
  2. 各阶段钩子函数:beforeCreate 到 unmounted
  3. 组合式 API 钩子:onMounted、onUnmounted 等
  4. 实际应用:数据获取、定时器、事件监听、错误处理
  5. 父子组件顺序:创建和卸载时的执行顺序

练习

  1. 创建一个自动刷新数据的组件
  2. 创建一个带有清理功能的组件
  3. 实现一个错误边界组件

准备好进入下一章,学习 ref 和 reactive 的内容了吗?