Vue的生命周期是指组件从创建到销毁的完整过程,分为创建、挂载、更新、销毁四个核心阶段,每个阶段对应特定的钩子函数,允许开发者在关键节点插入自定义逻辑。
阶段 | Vue 2 钩子函数 | Vue 3 Composition API 钩子 | 核心作用 |
|---|---|---|---|
创建阶段 |
| - | 实例初始化后,数据观测与事件配置前调用,无法访问 |
创建阶段 |
| - | 实例创建完成,可访问响应式数据与方法,适合初始化数据、发起异步请求 ^^8^^^ |
挂载阶段 |
|
| 模板编译完成,即将挂载到DOM前调用,未生成真实DOM ^ |
挂载阶段 |
|
| 组件挂载到DOM后调用,可安全操作DOM、初始化第三方库 ^ |
更新阶段 |
|
| 数据更新后、DOM重新渲染前调用,可获取更新前的状态 ^ |
更新阶段 |
|
| DOM更新完成后调用,可执行依赖新DOM的操作 ^ |
销毁阶段 |
|
| 组件销毁前调用,用于清理定时器、事件监听等资源 ^ |
销毁阶段 |
|
| 组件销毁后调用,所有事件监听器与子实例已被移除 ^ |
activated/deactivated:配合<keep-alive>使用,组件激活/停用时调用,用于缓存组件状态
errorCaptured:捕获子孙组件抛出的错误,可用于全局异常处理与日志上报
1. beforeCreate:全局事件总线初始化
在Vue 2中,可在beforeCreate中创建全局事件总线,实现跨组件通信:
// main.js
import Vue from 'vue'
import App from './App.vue'
Vue.prototype.$bus = new Vue()
new Vue({
beforeCreate() {
this.$bus.$on('global-event', (data) => {
console.log('全局事件触发:', data)
})
},
render: h => h(App)
}).$mount('#app')
在组件销毁前需移除事件监听,避免内存泄漏:
// 组件内
export default {
beforeDestroy() {
this.$bus.$off('global-event')
}
}
2. created:异步数据初始化
created是发起异步请求的理想时机,此时数据已初始化但DOM未渲染,可并行执行数据获取与模板编译,提升页面加载速度:
// Vue 2 Options API
export default {
data() {
return {
productList: []
}
},
async created() {
try {
const response = await fetch('/api/products')
this.productList = await response.json()
} catch (error) {
console.error('数据获取失败:', error)
}
}
}
// Vue 3 Composition API
import { ref, onMounted } from 'vue'
export default {
setup() {
const productList = ref([])
const fetchData = async () => {
try {
const response = await fetch('/api/products')
productList.value = await response.json()
} catch (error) {
console.error('数据获取失败:', error)
}
}
// Vue 3中created逻辑可直接在setup中执行
fetchData()
return { productList }
}
}
^^^8^^^
1. mounted:DOM操作与第三方组件初始化
mounted是操作真实DOM的第一个时机,适合初始化地图、图表、轮播图等第三方库:
// 初始化ECharts图表
import { ref, onMounted } from 'vue'
import * as echarts from 'echarts'
export default {
setup() {
const chartRef = ref(null)
let chartInstance = null
onMounted(() => {
chartInstance = echarts.init(chartRef.value)
chartInstance.setOption({
title: { text: '用户消费趋势' },
xAxis: { type: 'category', data: ['1月', '2月', '3月'] },
yAxis: { type: 'value' },
series: [{ type: 'line', data: [120, 200, 150] }]
})
})
onUnmounted(() => {
chartInstance?.dispose() // 组件销毁时销毁图表实例
})
return { chartRef }
}
}
^
2. beforeMount:复杂数据预处理
在beforeMount中执行复杂数据计算,减少首次渲染时的计算量,提升页面加载性能:
export default {
data() {
return {
rawData: [],
processedData: []
}
},
beforeMount() {
// 预处理复杂数据,避免在渲染阶段阻塞UI
this.processedData = this.rawData.map(item => ({
...item,
formattedDate: new Date(item.date).toLocaleDateString(),
total: item.price * item.quantity
}))
}
}
注意:beforeMount中不可直接操作DOM,若需确保DOM已更新,可使用this.$nextTick 。
1. beforeUpdate:状态快照与性能优化
在数据更新前保存状态快照,或判断数据是否真的需要更新,避免不必要的DOM渲染:
import { ref, onBeforeUpdate } from 'vue'
export default {
setup() {
const stockPrice = ref(100)
let prevPrice = 100
onBeforeUpdate(() => {
prevPrice = stockPrice.value
// 若价格变化小于0.1%,取消更新
if (Math.abs(stockPrice.value - prevPrice) / prevPrice < 0.001) {
stockPrice.value = prevPrice // 阻止不必要的更新
}
})
return { stockPrice }
}
}
2. updated:第三方库状态同步
当DOM更新完成后,同步第三方库的状态,如富文本编辑器、地图组件等:
import { ref, onUpdated } from 'vue'
export default {
setup() {
const editorContent = ref('')
onUpdated(() => {
// 同步富文本编辑器内容
const editor = document.getElementById('rich-editor')
if (editor) {
editor.innerHTML = editorContent.value
}
})
return { editorContent }
}
}
注意:避免在updated中直接修改响应式数据,否则会触发无限更新循环 ^。
beforeUnmount(Vue 3)或beforeDestroy(Vue 2)是清理资源的关键时机,需释放所有手动创建的资源:
import { ref, onMounted, onUnmounted } from 'vue'
export default {
setup() {
const timerId = ref(null)
const resizeHandler = () => {
console.log('窗口大小变化')
}
onMounted(() => {
// 启动定时器
timerId.value = setInterval(() => {
console.log('定时器执行')
}, 1000)
// 绑定窗口 resize 事件
window.addEventListener('resize', resizeHandler)
})
onUnmounted(() => {
// 清理定时器
if (timerId.value) {
clearInterval(timerId.value)
}
// 移除事件监听
window.removeEventListener('resize', resizeHandler)
})
return {}
}
}
<!-- ProductDetail.vue -->
<template>
<div class="product-detail">
<div class="product-images">
<img v-for="img in product.images" :key="img.id" :data-src="img.url" class="lazy-image" alt="商品图片">
</div>
<div class="product-info">
<h2>{{ product.name }}</h2>
<p class="price" :class="{ 'price-drop': isPriceDrop }">¥{{ product.price }}</p>
<div class="quantity-selector">
<button @click="decreaseQuantity">-</button>
<span>{{ quantity }}</span>
<button @click="increaseQuantity">+</button>
</div>
<button @click="addToCart">加入购物车</button>
</div>
</div>
</template>
<script>
import { ref, onMounted, onUnmounted, onBeforeUpdate } from 'vue'
import lazysizes from 'lazysizes'
export default {
props: {
productId: {
type: Number,
required: true
}
},
setup(props) {
const product = ref({})
const quantity = ref(1)
const prevPrice = ref(0)
const isPriceDrop = ref(false)
let timerId = null
// 获取商品数据
const fetchProduct = async () => {
try {
const response = await fetch(`/api/products/${props.productId}`)
product.value = await response.json()
prevPrice.value = product.value.price
} catch (error) {
console.error('商品数据获取失败:', error)
}
}
// 初始化图片懒加载
const initLazyLoad = () => {
lazysizes.init()
}
// 价格变化检测
onBeforeUpdate(() => {
if (product.value.price !== prevPrice.value) {
isPriceDrop.value = product.value.price < prevPrice.value
prevPrice.value = product.value.price
// 价格变化提示
alert(`商品价格变动:¥${prevPrice.value} → ¥${product.value.price}`)
}
})
// 模拟价格波动
const simulatePriceChange = () => {
timerId = setInterval(() => {
product.value.price = (Math.random() * 50 + 100).toFixed(2)
}, 10000)
}
// 购物车操作
const addToCart = () => {
console.log(`加入购物车:${product.value.name} × ${quantity.value}`)
}
const increaseQuantity = () => quantity.value++
const decreaseQuantity = () => {
if (quantity.value > 1) quantity.value--
}
onMounted(() => {
fetchProduct()
initLazyLoad()
simulatePriceChange()
})
onUnmounted(() => {
if (timerId) clearInterval(timerId)
})
return {
product,
quantity,
isPriceDrop,
addToCart,
increaseQuantity,
decreaseQuantity
}
}
}
</script>
created/setup:发起商品数据请求,实现数据与DOM渲染并行
mounted:初始化图片懒加载库,启动价格波动模拟定时器 ^
beforeUpdate:检测商品价格变化,提示用户价格变动 ^
beforeUnmount:清理价格波动定时器,避免内存泄漏
优先在created中发起异步请求,避免在mounted中请求导致DOM渲染延迟
复杂数据预处理放在beforeMount,减少首次渲染计算量
避免在updated中执行复杂操作,优先使用watch监听特定数据变化
使用watch替代updated
// 不推荐:updated会在所有数据变化时触发
onUpdated(() => {
if (someData.value) {
// 执行逻辑
}
})
// 推荐:仅监听特定数据变化
watch(someData, (newValue) => {
if (newValue) {
// 执行逻辑
}
})
利用<keep-alive>缓存组件状态配合activated/deactivated钩子,避免组件重复创建与销毁:
<template>
<keep-alive>
<router-view v-if="$route.meta.keepAlive" />
</keep-alive>
<router-view v-if="!$route.meta.keepAlive" />
</template>
<script>
export default {
activated() {
// 组件激活时恢复状态
console.log('组件激活')
},
deactivated() {
// 组件停用时保存状态
console.log('组件停用')
}
}
</script>
^
手动绑定的事件监听、定时器、WebSocket连接等,必须在beforeUnmount中清理 ^
第三方库实例(如ECharts、地图)需调用销毁方法释放资源 ^
避免在组件中保留对DOM元素的长期引用
beforeMount、mounted仅在客户端执行,SSR项目中需避免在这些钩子中执行服务端无法处理的逻辑
数据请求优先放在created或专门的SSR数据获取钩子(如asyncData)中
添加日志打印
import { onBeforeMount, onMounted, onBeforeUpdate, onUpdated, onBeforeUnmount, onUnmounted } from 'vue'
export default {
setup() {
const log = (msg) => console.log(`[生命周期] ${msg}`)
onBeforeMount(() => log('beforeMount'))
onMounted(() => log('mounted'))
onBeforeUpdate(() => log('beforeUpdate'))
onUpdated(() => log('updated'))
onBeforeUnmount(() => log('beforeUnmount'))
onUnmounted(() => log('onUnmounted'))
return {}
}
}
使用Vue DevTools通过Vue DevTools的“组件”面板,可实时查看组件当前生命周期阶段,以及数据变化历史。
单一职责原则:每个钩子函数专注于一类逻辑,避免在一个钩子中执行过多操作
资源清理优先:组件销毁时必须清理所有手动创建的资源,养成“创建-清理”成对编写的习惯
性能优先:避免在渲染阶段执行复杂计算,尽量将逻辑前置到created或beforeMount
兼容性考虑:Vue 3项目中优先使用Composition API钩子,提升代码可组合性与复用性
避免重复请求:在created和mounted中避免重复发起相同的异步请求 </doc_start> 以上教程从生命周期核心概念、实战应用场景、电商项目案例、性能优化策略到调试技巧,全方位覆盖Vue生命周期的实际应用。您可以根据项目需求,灵活选择对应的钩子函数与优化方案,在保证功能实现的同时,提升应用性能与可维护性。在实际开发中,建议先在测试环境验证生命周期逻辑,再逐步推广到生产环境,确保应用稳定运行。