每个 Vue 组件实例在创建时都会经历一系列的初始化步骤,这个过程被称为组件的生命周期。Vue 在生命周期的各个关键时刻提供了钩子函数,允许我们在特定阶段执行自定义逻辑。
Vue 组件的生命周期可以分为四个主要阶段:
初始化数据观测、事件、计算属性等
将模板编译成渲染函数,创建虚拟DOM,挂载到真实DOM
响应式数据变化时,重新渲染组件
组件实例被销毁,清理事件监听器、定时器等
生命周期钩子允许我们在组件生命周期的特定时刻执行代码。理解这些钩子的执行顺序对于编写正确的组件逻辑至关重要。
Vue 3 提供了两种API风格:Options API 和 Composition API。以下是两种API中生命周期钩子的对应关系:
| 生命周期阶段 | Options API | Composition API | 执行时机 |
|---|---|---|---|
| 创建前 | beforeCreate |
setup() 替代 |
实例初始化后,数据观测之前 |
| 创建后 | created |
setup() 替代 |
实例创建完成,数据观测已建立 |
| 挂载前 | beforeMount |
onBeforeMount |
挂载开始之前,render 函数首次调用 |
| 挂载后 | mounted |
onMounted |
实例挂载到 DOM 之后 |
| 更新前 | beforeUpdate |
onBeforeUpdate |
数据变化,虚拟 DOM 重新渲染之前 |
| 更新后 | updated |
onUpdated |
数据变化,虚拟 DOM 重新渲染之后 |
| 卸载前 | beforeUnmount (Vue 3)beforeDestroy (Vue 2) |
onBeforeUnmount |
实例销毁之前 |
| 卸载后 | unmounted (Vue 3)destroyed (Vue 2) |
onUnmounted |
实例销毁之后 |
| 激活 | activated |
onActivated |
被 keep-alive 缓存的组件激活时 |
| 停用 | deactivated |
onDeactivated |
被 keep-alive 缓存的组件停用时 |
在 Composition API 中,生命周期钩子作为函数从 vue 包中导入,并在 setup() 函数中调用。
import {
onBeforeMount,
onMounted,
onBeforeUpdate,
onUpdated,
onBeforeUnmount,
onUnmounted,
onActivated,
onDeactivated,
onErrorCaptured
} from 'vue'
export default {
setup() {
// 生命周期钩子需要在 setup() 中同步调用
onBeforeMount(() => {
console.log('组件挂载前')
})
onMounted(() => {
console.log('组件已挂载')
})
onBeforeUpdate(() => {
console.log('组件更新前')
})
onUpdated(() => {
console.log('组件已更新')
})
onBeforeUnmount(() => {
console.log('组件卸载前')
})
onUnmounted(() => {
console.log('组件已卸载')
})
onActivated(() => {
console.log('组件被激活')
})
onDeactivated(() => {
console.log('组件被停用')
})
onErrorCaptured((err, instance, info) => {
console.error('捕获到错误:', err)
// 返回 false 阻止错误继续向上传播
return false
})
return {
// 返回模板中需要使用的数据和方法
}
}
}
在 Composition API 中,beforeCreate 和 created 的生命周期被 setup() 函数替代。setup() 在组件创建之前执行,是 Composition API 的入口点。
import { ref } from 'vue'
export default {
setup(props, context) {
// 这个阶段相当于 beforeCreate 和 created
console.log('setup() 执行 - 组件初始化')
// 在这里定义响应式数据、计算属性、方法等
const count = ref(0)
const message = ref('Hello Vue')
// 访问 props
console.log('props:', props)
// 访问上下文 (attrs, slots, emit)
console.log('context:', context)
// 返回模板中需要使用的数据和方法
return {
count,
message
}
}
}
onBeforeMount 在组件挂载到 DOM 之前调用,此时模板已编译但尚未渲染为真实 DOM。onMounted 在组件挂载到 DOM 之后调用,可以访问 DOM 元素。
import { ref, onBeforeMount, onMounted } from 'vue'
export default {
setup() {
const count = ref(0)
const containerRef = ref(null)
onBeforeMount(() => {
console.log('onBeforeMount: 组件即将挂载')
// 此时无法访问 DOM 元素
console.log('containerRef:', containerRef.value) // null
})
onMounted(() => {
console.log('onMounted: 组件已挂载')
// 此时可以访问 DOM 元素
console.log('containerRef:', containerRef.value) // DOM 元素
// 常见的 mounted 使用场景:
// 1. 初始化第三方库
// 2. 发送初始数据请求
// 3. 添加事件监听器
// 4. 操作 DOM 元素
})
return {
count,
containerRef
}
}
}
当响应式数据变化导致组件需要重新渲染时,会触发更新阶段的生命周期钩子。
import { ref, onBeforeUpdate, onUpdated } from 'vue'
export default {
setup() {
const count = ref(0)
const message = ref('')
onBeforeUpdate(() => {
console.log('onBeforeUpdate: 组件即将更新')
console.log('当前 count:', count.value)
// 此时 DOM 还未更新
})
onUpdated(() => {
console.log('onUpdated: 组件已更新')
console.log('更新后的 count:', count.value)
// 此时 DOM 已更新
// 注意:避免在 updated 中修改状态,可能导致无限循环
})
const increment = () => {
count.value++
}
return {
count,
message,
increment
}
}
}
onUpdated 会在每次组件更新后调用,包括子组件的更新。要避免在这里修改状态,否则可能导致无限更新循环。
当组件被销毁时,会触发卸载阶段的生命周期钩子。
import { ref, onBeforeUnmount, onUnmounted } from 'vue'
export default {
setup() {
const timerId = ref(null)
// 在 mounted 中设置定时器
onMounted(() => {
timerId.value = setInterval(() => {
console.log('定时器执行')
}, 1000)
})
onBeforeUnmount(() => {
console.log('onBeforeUnmount: 组件即将卸载')
// 此时组件实例仍完全可用
})
onUnmounted(() => {
console.log('onUnmounted: 组件已卸载')
// 清理定时器,避免内存泄漏
if (timerId.value) {
clearInterval(timerId.value)
timerId.value = null
}
// 其他清理工作:
// 1. 移除事件监听器
// 2. 取消网络请求
// 3. 清理第三方库实例
// 4. 取消订阅
})
return {}
}
}
当组件被 <keep-alive> 包裹时,会有两个额外的生命周期钩子:
import { onActivated, onDeactivated } from 'vue'
export default {
setup() {
const activeCount = ref(0)
onActivated(() => {
console.log('onActivated: 组件被激活')
activeCount.value++
// 常见用途:
// 1. 恢复定时器
// 2. 重新获取数据
// 3. 重新计算位置
})
onDeactivated(() => {
console.log('onDeactivated: 组件被停用')
// 常见用途:
// 1. 暂停定时器
// 2. 保存组件状态
// 3. 取消未完成的请求
})
return {
activeCount
}
}
}
// 父组件中使用 keep-alive
// <keep-alive>
// <my-component v-if="show" />
// </keep-alive>
onErrorCaptured 用于捕获来自子组件的错误。
import { onErrorCaptured } from 'vue'
export default {
setup() {
onErrorCaptured((err, instance, info) => {
// err: 错误对象
// instance: 触发错误的组件实例
// info: 错误信息字符串,如 'render function', 'event handler'
console.error('捕获到错误:', err)
console.log('组件实例:', instance)
console.log('错误信息:', info)
// 可以在此处发送错误到错误监控服务
sendErrorToMonitoringService(err)
// 返回 false 阻止错误继续向上传播
return false
})
return {}
}
}
对于仍然使用 Options API 的项目,以下是生命周期钩子的使用方式:
export default {
data() {
return {
count: 0,
message: 'Hello Vue'
}
},
beforeCreate() {
console.log('beforeCreate: 实例初始化后,数据观测之前')
// 此时 this 还不能访问 data、computed、methods 等
console.log('this.count:', this.count) // undefined
},
created() {
console.log('created: 实例创建完成')
// 此时可以访问 data、computed、methods 等
console.log('this.count:', this.count) // 0
// 常见用途:发送初始数据请求
this.fetchInitialData()
},
beforeMount() {
console.log('beforeMount: 挂载之前')
// 此时模板已编译,但尚未挂载到 DOM
},
mounted() {
console.log('mounted: 实例已挂载')
// 此时可以访问 DOM 元素
console.log('this.$el:', this.$el)
},
beforeUpdate() {
console.log('beforeUpdate: 数据更新,DOM 重新渲染之前')
console.log('当前 count:', this.count)
},
updated() {
console.log('updated: 数据更新,DOM 重新渲染之后')
console.log('更新后的 count:', this.count)
},
beforeUnmount() { // Vue 3
// beforeDestroy() { // Vue 2
console.log('beforeUnmount: 实例销毁之前')
// 此时组件实例仍完全可用
},
unmounted() { // Vue 3
// destroyed() { // Vue 2
console.log('unmounted: 实例销毁之后')
// 执行清理工作
},
activated() {
console.log('activated: 被 keep-alive 缓存的组件激活时')
},
deactivated() {
console.log('deactivated: 被 keep-alive 缓存的组件停用时')
},
errorCaptured(err, instance, info) {
console.error('捕获到错误:', err)
return false // 阻止错误继续向上传播
},
methods: {
fetchInitialData() {
// 发送初始数据请求
}
}
}
import { ref, onMounted, onUnmounted } from 'vue'
export default {
setup() {
const posts = ref([])
const isLoading = ref(false)
const error = ref(null)
let abortController = null
const fetchPosts = async () => {
isLoading.value = true
error.value = null
// 创建 AbortController 用于取消请求
abortController = new AbortController()
try {
const response = await fetch('https://api.example.com/posts', {
signal: abortController.signal
})
if (!response.ok) throw new Error('网络响应错误')
posts.value = await response.json()
} catch (err) {
if (err.name !== 'AbortError') {
error.value = err.message
}
} finally {
isLoading.value = false
}
}
// 组件挂载时获取数据
onMounted(() => {
fetchPosts()
})
// 组件卸载时取消请求
onUnmounted(() => {
if (abortController) {
abortController.abort()
}
})
return {
posts,
isLoading,
error
}
}
}
import { ref, onMounted, onUnmounted } from 'vue'
export default {
setup() {
const windowWidth = ref(window.innerWidth)
const mousePosition = ref({ x: 0, y: 0 })
const handleResize = () => {
windowWidth.value = window.innerWidth
}
const handleMouseMove = (event) => {
mousePosition.value = {
x: event.clientX,
y: event.clientY
}
}
// 组件挂载时添加事件监听器
onMounted(() => {
window.addEventListener('resize', handleResize)
document.addEventListener('mousemove', handleMouseMove)
// 初始化数据
handleResize()
})
// 组件卸载时移除事件监听器
onUnmounted(() => {
window.removeEventListener('resize', handleResize)
document.removeEventListener('mousemove', handleMouseMove)
})
return {
windowWidth,
mousePosition
}
}
}
created 或 setup() 中初始化数据,而不是 beforeCreatemounted 或 onMounted 中进行 DOM 操作mounted 或 onMounted 中发送初始数据请求beforeUnmount 或 onBeforeUnmount 中执行清理updated 或 onUpdated 中修改状态mounted 中初始化第三方库,在 beforeUnmount 中销毁点击按钮观察生命周期钩子的执行顺序:
在 Composition API 中,setup() 函数替代了 beforeCreate 和 created 钩子。setup() 在组件创建之前执行,可以在这里进行所有初始化工作。
updated 钩子会在组件更新后调用,如果在其中修改响应式数据,会触发新一轮的更新,导致无限循环。
当组件被 <keep-alive> 包裹时,使用 activated 和 deactivated 钩子来管理组件的激活和停用状态,如恢复/暂停定时器、重新获取数据等。
Vue 组件的生命周期顺序是:父组件 beforeCreate → 父组件 created → 父组件 beforeMount → 子组件生命周期 → 父组件 mounted。
Vue.js 的生命周期钩子提供了在组件各个阶段执行代码的能力。理解每个钩子的执行时机和用途对于编写正确的 Vue 组件至关重要。
关键要点:
无论使用 Options API 还是 Composition API,理解生命周期原理都能帮助你编写更健壮、更高效的 Vue 应用。
beforeUnmount 或 onBeforeUnmount 中清理定时器和事件监听器?setup() 函数对应 Options API 中的哪些生命周期钩子?