Vue 中的计算属性(computed)
概述
计算属性是 Vue 中一个强大的特性,用于声明式地处理响应式数据的复杂逻辑。它基于 Vue 实例的响应式数据进行计算,并缓存计算结果,只有在依赖的响应式数据发生变化时才会重新计算。
基本用法
1. 定义计算属性
javascript
new Vue({
data() {
return {
firstName: '张',
lastName: '三',
quantity: 2,
price: 100
}
},
computed: {
// 基本语法 - 计算属性的 getter
fullName() {
return this.firstName + ' ' + this.lastName
},
// 依赖多个数据源
totalPrice() {
return this.quantity * this.price
}
}
})
2. 在模板中使用
html
<template>
<div>
<p>姓名:{{ fullName }}</p>
<p>总价:{{ totalPrice }}</p>
</div>
</template>
主要特性
1. 缓存机制
计算属性会缓存计算结果,只有当依赖的响应式数据发生变化时才会重新计算。
javascript
computed: {
// 这个计算属性会缓存结果
now() {
return Date.now() // ❌ 错误示例:实际上不会更新,因为不依赖响应式数据
},
// 正确示例:依赖响应式数据
reversedMessage() {
return this.message.split('').reverse().join('')
}
}
2. Getter 和 Setter
计算属性默认只有 getter,但也可以提供 setter。
javascript
computed: {
fullName: {
// getter
get() {
return this.firstName + ' ' + this.lastName
},
// setter
set(newValue) {
const names = newValue.split(' ')
this.firstName = names[0]
this.lastName = names[names.length - 1]
}
}
}
// 使用 setter
this.fullName = '李 四' // 会自动更新 firstName 和 lastName
与方法的区别
计算属性 vs 方法
javascript
// 计算属性
computed: {
computedNow() {
return this.message + ' ' + Date.now()
// 只有当 message 变化时才会重新计算
}
}
// 方法
methods: {
methodNow() {
return this.message + ' ' + Date.now()
// 每次调用都会重新计算
}
}
主要区别:
- 计算属性有缓存,方法没有
- 计算属性基于响应式依赖,方法不自动追踪依赖
- 计算属性在模板中像属性一样使用,方法需要加括号调用
与侦听器(watch)的区别
适用场景对比
javascript
// 使用计算属性 - 适合同步计算
computed: {
fullName() {
return this.firstName + ' ' + this.lastName
}
}
// 使用侦听器 - 适合异步操作或副作用
watch: {
firstName(newVal, oldVal) {
// 执行异步操作或复杂逻辑
this.fetchUserData(newVal)
}
}
实际应用示例
示例1:购物车计算
javascript
new Vue({
data() {
return {
cartItems: [
{ name: '商品A', price: 100, quantity: 2 },
{ name: '商品B', price: 200, quantity: 1 },
{ name: '商品C', price: 50, quantity: 3 }
]
}
},
computed: {
// 计算总价
totalPrice() {
return this.cartItems.reduce((sum, item) => {
return sum + (item.price * item.quantity)
}, 0)
},
// 计算商品总数
totalItems() {
return this.cartItems.reduce((sum, item) => {
return sum + item.quantity
}, 0)
},
// 是否有折扣资格(总价超过500)
hasDiscount() {
return this.totalPrice > 500
},
// 折后价格
finalPrice() {
return this.hasDiscount ? this.totalPrice * 0.9 : this.totalPrice
}
}
})
示例2:列表过滤和排序
javascript
new Vue({
data() {
return {
products: [
{ name: '手机', price: 2999, category: '电子产品' },
{ name: '衣服', price: 299, category: '服装' },
{ name: '电脑', price: 5999, category: '电子产品' },
{ name: '鞋子', price: 399, category: '服装' }
],
selectedCategory: '',
sortBy: 'price',
sortOrder: 'asc'
}
},
computed: {
// 过滤产品
filteredProducts() {
if (!this.selectedCategory) return this.products
return this.products.filter(product =>
product.category === this.selectedCategory
)
},
// 排序产品
sortedProducts() {
return [...this.filteredProducts].sort((a, b) => {
let result = 0
if (a[this.sortBy] < b[this.sortBy]) result = -1
if (a[this.sortBy] > b[this.sortBy]) result = 1
return this.sortOrder === 'asc' ? result : -result
})
},
// 价格统计
priceStats() {
const prices = this.filteredProducts.map(p => p.price)
return {
min: Math.min(...prices),
max: Math.max(...prices),
average: prices.reduce((a, b) => a + b, 0) / prices.length
}
}
}
})
最佳实践
1. 保持纯函数
计算属性的 getter 应该是纯函数,不要有副作用。
javascript
// ✅ 正确
computed: {
validItems() {
return this.items.filter(item => item.isValid)
}
}
// ❌ 避免
computed: {
processItems() {
this.items.forEach(item => {
item.processed = true // 副作用!
})
return this.items
}
}
2. 避免复杂计算
如果计算过于复杂,考虑拆分成多个计算属性或使用方法。
3. 命名清晰
使用描述性的名称,反映计算属性的用途。
javascript
// ✅ 清晰
computed: {
isFormValid() { /* ... */ },
formattedDate() { /* ... */ }
}
// ❌ 不清晰
computed: {
check() { /* ... */ },
format() { /* ... */ }
}
Vue 3 中的计算属性
在 Vue 3 的组合式 API 中:
javascript
import { ref, computed } from 'vue'
export default {
setup() {
const firstName = ref('张')
const lastName = ref('三')
// 只读计算属性
const fullName = computed(() => {
return firstName.value + ' ' + lastName.value
})
// 可写计算属性
const writableFullName = computed({
get: () => firstName.value + ' ' + lastName.value,
set: (newValue) => {
const [first, last] = newValue.split(' ')
firstName.value = first
lastName.value = last
}
})
return { fullName, writableFullName }
}
}
常见注意事项
- 不要在计算属性中修改依赖的数据 - 这可能导致无限循环
- 计算属性不能异步 - 如果需要异步计算,考虑使用侦听器或方法
- 依赖追踪是自动的 - 只追踪在 getter 中实际使用的响应式属性
- 避免在计算属性中执行高开销操作 - 利用缓存特性优化性能
计算属性是 Vue 响应式系统的核心特性之一,合理使用可以大大简化代码逻辑,提高应用性能。