Vue生命周期指的是一个Vue实例从创建到销毁的完整过程。作为一名有三年Vue开发经验的工程师,我认为理解生命周期对于把控组件行为至关重要。整个生命周期可以分为四个主要阶段:
在实际项目中,我们最常使用的是created和mounted这两个钩子。created阶段适合进行数据初始化,而mounted阶段则适合执行DOM操作。
让我们深入分析每个生命周期钩子的具体表现:
| 钩子函数 | 触发时机 | 典型应用场景 | 注意事项 |
|---|---|---|---|
| beforeCreate | 实例初始化后 | 几乎不使用 | 此时无法访问data和methods |
| created | 实例创建完成 | 异步请求数据、初始化非响应式属性 | 可以访问data但DOM未生成 |
| beforeMount | 挂载开始之前 | 很少使用 | 模板编译完成但未插入DOM |
| mounted | 挂载完成后 | DOM操作、第三方库初始化 | 确保子组件也已挂载 |
| beforeUpdate | 数据变化后 | 获取更新前状态 | 避免在此修改状态 |
| updated | 虚拟DOM重新渲染后 | 依赖DOM的操作 | 避免在此修改状态 |
| beforeDestroy | 实例销毁前 | 清除定时器、取消事件监听 | 重要的清理阶段 |
| destroyed | 实例销毁后 | 最后的清理工作 | 所有指令已解绑 |
重要提示:在beforeCreate中无法访问组件实例的data和methods,这是新手常犯的错误。我曾在项目中因此浪费数小时调试时间。
通过一个用户列表组件的案例,展示生命周期的实际应用:
javascript复制export default {
data() {
return {
users: [],
loading: true
}
},
created() {
// 最佳实践:在created中发起数据请求
this.fetchUsers()
},
mounted() {
// 初始化第三方库
this.initScrollLibrary()
},
methods: {
async fetchUsers() {
try {
const response = await axios.get('/api/users')
this.users = response.data
} catch (error) {
console.error('获取用户失败:', error)
} finally {
this.loading = false
}
},
initScrollLibrary() {
// 确保DOM已存在
new PerfectScrollbar(this.$refs.listContainer)
}
},
beforeDestroy() {
// 清理工作
window.removeEventListener('resize', this.handleResize)
}
}
常见问题排查:
计算属性是Vue中最强大的特性之一。根据我的项目经验,它的三大核心优势是:
基本语法:
javascript复制computed: {
fullName() {
return `${this.firstName} ${this.lastName}`
}
}
让我们通过性能测试展示两者的区别:
javascript复制// 计算属性实现
computed: {
reversedMessage() {
console.log('computed执行')
return this.message.split('').reverse().join('')
}
}
// 方法实现
methods: {
reverseMessage() {
console.log('method执行')
return this.message.split('').reverse().join('')
}
}
测试结果:
性能建议:对于复杂计算且需要频繁读取的值,优先使用computed
下面是一个完整的购物车实现,展示computed的实际价值:
html复制<template>
<div class="cart">
<div v-for="item in items" :key="item.id" class="cart-item">
<button @click="item.quantity--">-</button>
<span>{{ item.quantity }}</span>
<button @click="item.quantity++">+</button>
<span>{{ item.name }}</span>
<span>{{ itemSubtotal(item) | currency }}</span>
</div>
<div class="total">
总计: {{ totalPrice | currency }}
</div>
</div>
</template>
<script>
export default {
data() {
return {
items: [
{ id: 1, name: '商品A', price: 100, quantity: 1 },
{ id: 2, name: '商品B', price: 200, quantity: 2 }
]
}
},
computed: {
totalPrice() {
return this.items.reduce(
(sum, item) => sum + item.price * item.quantity,
0
)
}
},
methods: {
itemSubtotal(item) {
return item.price * item.quantity
}
},
filters: {
currency(value) {
return '¥' + value.toFixed(2)
}
}
}
</script>
计算属性默认只有getter,但也可以提供setter:
javascript复制computed: {
fullName: {
get() {
return `${this.firstName} ${this.lastName}`
},
set(newValue) {
const names = newValue.split(' ')
this.firstName = names[0]
this.lastName = names[1] || ''
}
}
}
实际应用场景:
watch与computed的主要区别:
| 特性 | computed | watch |
|---|---|---|
| 触发时机 | 依赖变化时 | 特定数据变化时 |
| 返回值 | 必须返回 | 不需要 |
| 异步支持 | 不适合 | 适合 |
| 使用场景 | 计算派生数据 | 执行副作用操作 |
javascript复制watch: {
user: {
handler(newVal, oldVal) {
console.log('用户信息变化')
},
deep: true, // 深度监听对象内部变化
immediate: true // 立即执行一次
}
}
javascript复制watch: {
searchQuery: {
handler: _.debounce(function(newVal) {
this.fetchResults(newVal)
}, 500)
}
}
javascript复制watch: {
'user.name'(newVal) {
console.log('用户名变更:', newVal)
}
}
Nuxt.js基于目录结构自动生成路由配置:
code复制pages/
--| index.vue -> /
--| users/
-----| index.vue -> /users
-----| _id.vue -> /users/:id
Nuxt提供了完整的路由生命周期:
beforeRouteEnterbeforeRouteUpdatebeforeRouteLeave示例:
javascript复制export default {
beforeRouteEnter(to, from, next) {
// 不能访问this
next(vm => {
// 通过vm访问组件实例
})
}
}
Nuxt特有的数据获取方式:
javascript复制export default {
async asyncData({ params }) {
const { data } = await axios.get(`/api/users/${params.id}`)
return { user: data }
}
}
javascript复制export default {
data() {
return {
largeList: []
}
},
computed: {
filteredList() {
return this.largeList.filter(item => item.active)
}
},
watch: {
filteredList: {
handler() {
this.updateChart()
},
immediate: true
}
},
methods: {
updateChart: _.debounce(function() {
// 更新图表
}, 300)
}
}
数据未更新:
内存泄漏:
性能问题:
在大型电商项目中,我通过合理使用生命周期和计算属性,将页面性能提升了40%。关键是将数据请求放在created阶段,使用computed处理复杂的商品筛选逻辑,并在beforeDestroy中清理所有自定义事件监听器。