一、 为什么你的Vue代码需要计算属性?
想象一下:你开了一家在线咖啡店,页面上需要展示一杯拿铁的总价。单价30元,用户可以选择数量,还要根据数量显示折扣——买3杯以上打9折。用methods实现的话,模板里可能要塞满calculateTotalPrice()、getDiscount()这类函数调用,不仅看起来乱,还可能触发不必要的重复计算。
这时候,Vue的计算属性(computed)就像个贴心小管家!它自动追踪依赖的数据(比如单价、数量),只有当这些数据变化时才会重新计算,否则直接缓存上次结果。比如下面这个经典场景:
<template>
<div>
<p>单价:{{ price }}元</p>
<p>数量:<input v-model.number="quantity"></p>
<p>总价:{{ totalPrice }}元</p> <!-- 看这里! -->
</div>
</template>
<script>
export default {
data() {
return {
price: 30,
quantity: 1
}
},
computed: {
totalPrice() {
console.log('重新计算总价') // 只有quantity变化时才会打印
return this.price * this.quantity
}
}
}
</script>
当你反复修改数量时,控制台只会在一开始打印一次——这就是计算属性的缓存魔法!相比methods每次渲染都执行,computed在性能上直接碾压。
二、 解剖计算属性:getter是乖宝宝,setter是叛逆少年?
大部分人只用过计算属性的默认写法,比如totalPrice() { return ... }。其实这是计算属性的简写形式,完整形态包含getter和setter两个方法:
1. getter——人见人爱的优等生
getter负责读取数据,当你模板中调用{{ totalPrice }}时,其实就是触发了getter方法。它必须返回一个值,而且默认情况下就是个纯纯的getter。
实战场景:用户输入全名,自动拆分成姓和名
<template>
<div>
<input v-model="fullName" placeholder="输入全名(如:张三)">
<p>姓:{{ lastName }}</p>
<p>名:{{ firstName }}</p>
</div>
</template>
<script>
export default {
data() {
return {
lastName: '',
firstName: ''
}
},
computed: {
fullName: {
// getter - 当读取fullName时组合数据
get() {
return `${this.lastName}${this.firstName}`
},
// setter - 当修改fullName时拆分数据
set(newValue) {
// 偷偷告诉你,中文名一般是姓1个字,名2个字
const names = newValue.split('')
this.lastName = names[0] || ''
this.firstName = names.slice(1).join('') || ''
}
}
}
}
</script>
试试在输入框打字,你会发现姓和名自动拆分好了!这就是setter在幕后偷偷干活。
2. setter——被低估的变形金刚
setter在修改计算属性时触发,接收一个新值作为参数。它的魔法在于能够反向更新其依赖的data数据!
进阶场景:购物车价格联动
<template>
<div>
<p>单价:{{ price }}元</p>
<p>
总价:<input v-model="finalPrice">元
<span>({{ quantity }}件)</span>
</p>
</div>
</template>
<script>
export default {
data() {
return {
price: 30,
quantity: 1
}
},
computed: {
finalPrice: {
get() {
return this.price * this.quantity
},
set(newPrice) {
// 根据总价反推数量,确保是整数
this.quantity = Math.max(1, Math.round(newPrice / this.price))
}
}
}
}
</script>
现在你可以直接修改总价输入框,数量会自动调整!这种双向联动用普通方法实现要写一堆代码,而setter几行就搞定了。
三、 实战全家桶:这些场景让你直呼“真香”
场景1:智能搜索过滤
<template>
<div>
<input v-model="searchKeyword" placeholder="搜索用户">
<ul>
<li v-for="user in filteredUsers" :key="user.id">
{{ user.name }} - {{ user.email }}
</li>
</ul>
</div>
</template>
<script>
export default {
data() {
return {
searchKeyword: '',
users: [
{ id: 1, name: '张三', email: 'zhang@email.com' },
{ id: 2, name: '李四', email: 'li@email.com' }
]
}
},
computed: {
filteredUsers() {
const keyword = this.searchKeyword.toLowerCase()
return this.users.filter(user =>
user.name.toLowerCase().includes(keyword) ||
user.email.toLowerCase().includes(keyword)
)
}
}
}
</script>
场景2:表单验证全家桶
<template>
<form @submit="submitForm">
<input v-model="userInfo.username" placeholder="用户名">
<span v-if="!validations.isUsernameValid" style="color:red">
用户名3-10位字符
</span>
<input v-model="userInfo.age" type="number" placeholder="年龄">
<span v-if="!validations.isAgeValid" style="color:red">
年龄必须成年
</span>
<button :disabled="!validations.isFormValid">提交</button>
</form>
</template>
<script>
export default {
data() {
return {
userInfo: {
username: '',
age: 0
}
}
},
computed: {
validations() {
return {
isUsernameValid: this.userInfo.username.length >= 3 &&
this.userInfo.username.length <= 10,
isAgeValid: this.userInfo.age >= 18,
isFormValid: this.isUsernameValid && this.isAgeValid
}
}
}
}
</script>
四、 避坑指南:setter不是万能的!
- 不要直接修改getter中的依赖数据
// 错误示范!
computed: {
fullName: {
get() {
// 不要在getter里修改依赖的数据
this.firstName = '默认名' // 大忌!
return `${this.lastName}${this.firstName}`
}
}
}
- setter中避免无限循环
computed: {
finalPrice: {
get() { return this.price * this.quantity },
set(newPrice) {
// 注意:这里修改quantity会触发finalPrice重新计算
this.quantity = Math.round(newPrice / this.price)
// 但不会导致无限循环,因为Vue有依赖追踪机制
}
}
}
- 复杂业务逻辑建议拆分成多个计算属性
// 好习惯:一个计算属性只负责一个功能
computed: {
basePrice() { return this.price * this.quantity },
discount() { return this.quantity > 3 ? 0.9 : 1 },
finalPrice() { return this.basePrice * this.discount }
}
五、 Vue 3组合式API写法(附赠彩蛋)
如果你已经在用Vue 3,计算属性可以写得更加优雅:
<template>
<div>
<input v-model="fullName" placeholder="输入全名">
<p>姓:{{ lastName }}</p>
<p>名:{{ firstName }}</p>
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
const lastName = ref('')
const firstName = ref('')
const fullName = computed({
get: () => `${lastName.value}${firstName.value}`,
set: (newValue) => {
const names = newValue.split('')
lastName.value = names[0] || ''
firstName.value = names.slice(1).join('') || ''
}
})
</script>
六、 总结:什么时候该请出getter/setter这对神仙组合?
- 只用getter:大多数场景,只需要根据数据计算显示值
- 启用setter:需要双向数据绑定、反向更新数据源、处理用户输入直接修改计算结果时
记住这个秘诀:getter让数据变聪明,setter让数据变灵活。下次当你需要让数据“既能读又能写”时,别犹豫,请果断祭出计算属性的完整形态!
3414

被折叠的 条评论
为什么被折叠?



