大家好!今天我们来聊聊 Vue.js 中两个非常重要的概念:computed 和 watch。很多 Vue 初学者甚至有一定经验的开发者,对这两个功能的使用场景和区别仍然存在困惑。
"什么时候用 computed?什么时候用 watch?" 这可能是 Vue 开发者最常遇到的问题之一。
一、先看一个真实场景
假设我们正在开发一个电商网站,需要计算购物车总价:
javascript
// 购物车数据
data() {
return {
cartItems: [
{ name: '商品A', price: 100, quantity: 2 },
{ name: '商品B', price: 200, quantity: 1 }
],
discount: 0.9 // 9折优惠
}
}
现在我们需要计算:
-
- 商品总价(单价×数量之和)
-
- 折后总价
该怎么实现呢?
二、Computed(计算属性):用于派生数据
computed 的核心思想:基于现有数据计算出一个新的数据值
javascript
computed: {
// 计算商品总价
totalPrice() {
return this.cartItems.reduce((sum, item) => {
return sum + item.price * item.quantity
}, 0)
},
// 计算折后价
finalPrice() {
return this.totalPrice * this.discount
}
}
computed 的特点:
-
- 声明式编程:你只需要告诉 Vue "我需要什么数据",Vue 会自动处理依赖和更新
-
- 缓存机制:只有当依赖的数据发生变化时,才会重新计算
-
- 同步计算:适合执行同步操作
-
- 返回一个新值:必须返回一个值
三、Watch(侦听器):用于观察数据变化
watch 的核心思想:当某个数据变化时,执行特定的操作
假设我们希望在购物车商品变化时,自动保存到本地存储:
javascript
watch: {
// 深度监听购物车变化
cartItems: {
handler(newVal, oldVal) {
// 保存到本地存储
localStorage.setItem('cart', JSON.stringify(newVal))
// 可以发送到服务器
this.saveCartToServer(newVal)
},
deep: true, // 深度监听
immediate: true // 立即执行一次
},
// 监听总价变化
totalPrice(newPrice) {
console.log(`总价变为:${newPrice}`)
if (newPrice > 1000) {
this.showDiscountTip() // 显示优惠提示
}
}
}
watch 的特点:
-
- 命令式编程:你告诉 Vue "当这个数据变化时,执行这些代码"
-
- 无缓存:每次变化都会执行
-
- 可以执行异步操作:适合 API 调用、复杂业务逻辑
-
- 不返回值:主要目的是执行副作用操作
四、核心区别对比表
| 特性 | computed | watch |
|---|---|---|
| 目的 | 派生新数据 | 响应数据变化 |
| 缓存 | ✅ 有缓存 | ❌ 无缓存 |
| 返回值 | ✅ 必须返回值 | ❌ 不返回值 |
| 异步 | ❌ 不支持异步 | ✅ 支持异步 |
| 语法 | 函数形式 | 对象或函数形式 |
| 使用场景 | 模板中的计算逻辑 | 数据变化时的副作用 |
五、什么时候用 computed?什么时候用 watch?
使用 computed 的场景:
-
- 模板中需要复杂表达式时
xml<!-- 不推荐 --> <div>{{ cartItems.reduce((sum, item) => sum + item.price, 0) }}</div> <!-- 推荐 --> <div>{{ totalPrice }}</div> -
- 一个数据依赖多个数据时
kotlincomputed: { fullName() { return this.firstName + ' ' + this.lastName } } -
- 需要缓存优化性能时
javascript// 复杂计算只会在依赖变化时执行 computed: { filteredList() { // 假设这是很耗时的筛选操作 return this.hugeList.filter(item => item.active) } }
使用 watch 的场景:
-
- 数据变化时需要执行异步操作
javascriptwatch: { searchQuery(newQuery) { // 防抖搜索 clearTimeout(this.timer) this.timer = setTimeout(() => { this.searchAPI(newQuery) }, 500) } } -
- 数据变化时需要执行复杂业务逻辑
javascriptwatch: { userLevel(newLevel, oldLevel) { if (newLevel === 'vip' && oldLevel !== 'vip') { this.showVIPWelcome() this.sendVIPNotification() } } } -
- 需要观察对象内部变化时
javascriptwatch: { formData: { handler() { this.validateForm() }, deep: true } }
六、常见误区和最佳实践
误区1:用 watch 实现本该用 computed 的功能
kotlin
// ❌ 不推荐:用 watch 计算全名
data() {
return {
firstName: '张',
lastName: '三',
fullName: ''
}
},
watch: {
firstName() {
this.fullName = this.firstName + this.lastName
},
lastName() {
this.fullName = this.firstName + this.lastName
}
}
// ✅ 推荐:用 computed 计算全名
computed: {
fullName() {
return this.firstName + ' ' + this.lastName
}
}
误区2:在 computed 中执行副作用操作
kotlin
// ❌ 不推荐:在 computed 中修改数据
computed: {
processedData() {
// 不要这样做!
this.someOtherData = 'changed'
return this.data.map(item => item * 2)
}
}
// ✅ 推荐:用 watch 执行副作用
watch: {
data(newData) {
this.someOtherData = 'changed'
}
}
七、性能考量
computed 的缓存机制是 Vue 性能优化的重要手段:
javascript
computed: {
// 假设这是一个计算量很大的函数
expensiveCalculation() {
console.log('重新计算!')
// 复杂计算...
return result
}
}
在模板中多次使用:
css
<div>{{ expensiveCalculation }}</div>
<div>{{ expensiveCalculation }}</div>
<div>{{ expensiveCalculation }}</div>
只会输出一次 "重新计算!",因为 computed 会缓存结果。
八、组合式 API 中的使用
在 Vue 3 的 Composition API 中,使用方式略有不同:
javascript
import { ref, computed, watch } from 'vue'
export default {
setup() {
const count = ref(0)
const doubleCount = computed(() => count.value * 2)
watch(count, (newValue, oldValue) => {
console.log(`count从${oldValue}变为${newValue}`)
})
return { count, doubleCount }
}
}
总结
记住这个简单的决策流程:
-
- 需要基于现有数据计算一个新值吗? → 用
computed
- 需要基于现有数据计算一个新值吗? → 用
-
- 需要在数据变化时执行某些操作吗? → 用
watch
- 需要在数据变化时执行某些操作吗? → 用
-
- 这个计算需要在模板中简洁表达吗? → 用
computed
- 这个计算需要在模板中简洁表达吗? → 用
-
- 需要处理异步操作或复杂业务逻辑吗? → 用
watch
- 需要处理异步操作或复杂业务逻辑吗? → 用
黄金法则:能用 computed 实现的,优先使用 computed;只有在需要"副作用"操作时,才使用 watch。
希望这篇文章能帮助你更好地理解和使用 Vue 中的 computed 和 watch!在实际开发中灵活运用这两个特性,能让你的代码更加清晰、高效。