模板内的表达式非常便利,但它们的设计初衷是用于简单运算。在模板中放入太多的逻辑会让模板过重且难以维护。对于任何复杂逻辑,都应该使用计算属性。
假设我们要显示一个字符串的反转:
<div id="app">
<p>原始字符串: {{ message }}</p>
<p>反转字符串: {{ message.split('').reverse().join('') }}</p>
</div>
模板变得臃肿,难以阅读。如果这个反转逻辑在多处使用,代码会重复。
使用计算属性:
<div id="app">
<p>原始字符串: {{ message }}</p>
<p>反转字符串: {{ reversedMessage }}</p>
</div>
<script>
new Vue({
el: '#app',
data: {
message: 'Hello Vue!'
},
computed: {
reversedMessage: function() {
return this.message.split('').reverse().join('')
}
}
})
</script>
代码更清晰,逻辑更易维护。
你可能注意到,用方法也能达到同样效果:
<p>反转字符串: {{ reverseMessage() }}</p>
<script>
new Vue({
methods: {
reverseMessage: function() {
return this.message.split('').reverse().join('')
}
}
})
</script>
两者的区别在于缓存:
计算属性基于它们的响应式依赖进行缓存
reversedMessage 会立即返回缓存结果,不必再次执行函数computed: {
now: function() {
return Date.now()
}
}
上面的计算属性不会更新,因为 Date.now() 不是响应式依赖。
计算属性默认只有 getter,需要时也可以提供 setter:
<div id="app">
<p>全名: {{ fullName }}</p>
<button @click="fullName = 'John Doe'">设置为 John Doe</button>
</div>
<script>
new Vue({
el: '#app',
data: {
firstName: 'Foo',
lastName: 'Bar'
},
computed: {
fullName: {
// getter
get: function() {
return this.firstName + ' ' + this.lastName
},
// setter
set: function(newValue) {
var names = newValue.split(' ')
this.firstName = names[0]
this.lastName = names[names.length - 1]
}
}
}
})
</script>
当运行 this.fullName = 'John Doe' 时,setter 会被调用,firstName 和 lastName 会被相应更新。
computed: {
formattedPrice: function() {
return '¥' + this.price.toFixed(2)
},
formattedDate: function() {
var date = new Date(this.timestamp)
return date.getFullYear() + '-' +
(date.getMonth() + 1) + '-' +
date.getDate()
}
}
computed: {
activeUsers: function() {
return this.users.filter(function(user) {
return user.isActive
})
},
sortedProducts: function() {
return this.products.slice().sort(function(a, b) {
return a.price - b.price
})
}
}
computed: {
cartTotal: function() {
return this.cartItems.reduce(function(total, item) {
return total + item.price * item.quantity
}, 0)
},
cartItemCount: function() {
return this.cartItems.reduce(function(count, item) {
return count + item.quantity
}, 0)
}
}
computed: {
isFormValid: function() {
return this.username.length >= 3 &&
this.email.includes('@') &&
this.password.length >= 6
},
usernameError: function() {
if (this.username.length === 0) return '请输入用户名'
if (this.username.length < 3) return '用户名至少3个字符'
return ''
}
}
computed: {
buttonClass: function() {
return {
'btn-primary': this.isValid,
'btn-disabled': !this.isValid,
'btn-loading': this.isLoading
}
},
progressStyle: function() {
return {
width: this.progress + '%',
backgroundColor: this.progress > 80 ? '#4caf50' : '#2196f3'
}
}
}
计算属性本身不能传参,但可以通过返回函数实现:
<div v-for="item in items" :key="item.id">
<span>{{ getItemTotal(item.id) }}</span>
</div>
<script>
new Vue({
computed: {
// 这不是真正的计算属性传参,而是返回一个函数
getItemTotal: function() {
var self = this
return function(id) {
var item = self.items.find(function(i) {
return i.id === id
})
return item ? item.price * item.quantity : 0
}
}
}
})
</script>
注意:这种方式返回的函数不会被缓存。如果需要缓存,考虑使用方法代替。
当需要在数据变化时执行异步或开销较大的操作时,使用侦听器更合适:
// 使用计算属性(适合简单转换)
computed: {
fullName: function() {
return this.firstName + ' ' + this.lastName
}
}
// 使用侦听器(适合异步操作)
watch: {
searchQuery: function(newVal) {
this.isLoading = true
this.debouncedSearch(newVal)
}
}
计算属性的缓存特性可以用于性能优化:
computed: {
// 这个计算属性只会在 items 改变时重新计算
expensiveResult: function() {
// 复杂的计算
return this.items.map(function(item) {
// 一些耗时的操作
}).filter(function(item) {
// 过滤
}).reduce(function(acc, item) {
// 聚合
}, 0)
}
}
如果用方法,每次调用都会重新计算。
| 特性 | 计算属性 | 方法 |
|---|---|---|
| 缓存 | 有 | 无 |
| 调用方式 | {{ prop }} | {{ method() }} |
| 适用场景 | 数据转换、格式化 | 需要每次执行、传参 |
计算属性是 Vue 响应式系统的重要组成部分,合理使用可以让代码更简洁高效。