在 Vue 开发中,computed
和 watch
是两个最常用的数据响应式工具。
但你是否也曾在以下场景中犹豫:
"这个逻辑该用
computed
还是watch
?"
本文将从原理、特性、性能、适用场景 四个维度,彻底讲清 computed
和 watch
的区别,助你做出最佳选择。
一、核心定位:本质区别
特性 | computed |
watch |
---|---|---|
本质 | 计算属性(声明式) | 侦听器(命令式) |
用途 | 基于现有数据"算出"新值 | 监听数据变化并"执行"副作用 |
类比 | Excel 公式 =A1+B1 |
单元格变化时的"宏"脚本 |
二、深入对比:五大核心差异
✅ 1. 缓存机制:性能的分水岭
computed |
watch |
|
---|---|---|
是否缓存 | ✅ 是(基于依赖) | ❌ 否 |
触发条件 | 仅当依赖项变化时重新计算 | 每次数据变化都执行 |
📌 示例:性能对比
js
export default {
data() {
return {
firstName: 'John',
lastName: 'Doe'
};
},
// ✅ computed:有缓存,高效
computed: {
fullName() {
console.log('computed fullName'); // 仅依赖变化时执行
return this.firstName + ' ' + this.lastName;
}
},
// ❌ watch:无缓存,频繁执行
watch: {
firstName(newVal, oldVal) {
console.log('watch firstName'); // 每次变化都执行
// 执行一些操作
}
}
}
结论 :
computed
更适合高频率读取的场景。
✅ 2. 异步支持:能力边界
computed |
watch |
|
---|---|---|
支持异步 | ❌ 否 | ✅ 是 |
📌 为什么 computed 不能异步?
js
// ❌ 错误示范
computed: {
async userInfo() {
const res = await fetch('/api/user');
return res.data;
}
}
computed
必须是同步函数,因为它在模板渲染时被调用;- 异步操作会导致返回
Promise
,视图无法正确渲染。
✅ watch 的异步优势
js
watch: {
userId: {
async handler(newId) {
this.loading = true;
try {
const res = await api.getUser(newId);
this.user = res.data;
} finally {
this.loading = false;
}
},
immediate: true // 立即执行
}
}
适用场景:API 调用、防抖搜索、数据同步。
✅ 3. 响应粒度:监听的精细度
computed |
watch |
|
---|---|---|
监听方式 | 自动追踪响应式依赖 | 手动指定监听路径 |
深度监听 | 天然支持(依赖自动收集) | 需 deep: true |
📌 深度监听示例
js
data() {
return {
user: { profile: { name: 'John' } }
};
},
// ✅ computed:自动追踪 user.profile.name
computed: {
displayName() {
return this.user.profile.name.toUpperCase();
}
},
// ✅ watch:需 deep 才能监听嵌套属性
watch: {
user: {
handler(newVal) {
console.log('user changed');
},
deep: true // 必须开启
}
}
注意 :
deep: true
有性能开销,应谨慎使用。
✅ 4. 写操作支持:双向能力
computed
不仅能读,还能写!
js
computed: {
fullName: {
// 读取时
get() {
return this.firstName + ' ' + this.lastName;
},
// 赋值时
set(newValue) {
const names = newValue.split(' ');
this.firstName = names[0];
this.lastName = names[1];
}
}
}
html
<input v-model="fullName" />
<!-- 输入 "Jane Smith" 会自动拆分到 firstName 和 lastName -->
watch
只能"监听",不能"拦截赋值"。
✅ 5. 执行时机:初始化行为
computed |
watch |
|
---|---|---|
初始执行 | 惰性计算(首次读取时) | 可 immediate: true 立即执行 |
js
watch: {
searchQuery: {
handler: 'fetchResults',
immediate: true // 组件创建时立即搜索
}
}
computed
的惰性特性避免了不必要的计算。
三、使用场景:何时用谁?
✅ 推荐使用 computed
的场景
场景 | 示例 |
---|---|
数据格式化 | fullName , priceWithTax |
条件计算 | filteredItems , hasItems |
组合多个数据源 | userStatus = user.loggedIn ? '在线' : '离线' |
模板中频繁使用 | 避免重复计算 |
js
computed: {
// 计算购物车总价
cartTotal() {
return this.cart.items.reduce((sum, item) => sum + item.price * item.qty, 0);
},
// 过滤激活的用户
activeUsers() {
return this.users.filter(user => user.isActive);
}
}
✅ 推荐使用 watch
的场景
场景 | 示例 |
---|---|
异步操作 | API 调用、定时器 |
昂贵操作 | 大数据处理、复杂计算 |
状态同步 | 本地存储 localStorage |
执行副作用 | 日志、通知、路由跳转 |
js
watch: {
// 防抖搜索
searchQuery: {
handler: _.debounce(function(query) {
this.fetchSearchResults(query);
}, 300),
immediate: true
},
// 同步到本地存储
userInfo: {
handler: function(newVal) {
localStorage.setItem('user', JSON.stringify(newVal));
},
deep: true
},
// 路由变化时刷新数据
'$route'(to, from) {
this.fetchData();
}
}
四、常见误区与最佳实践
❌ 误区 1:在 computed
中发起网络请求
js
// ❌ 错误
computed: {
userData() {
fetch('/api/user'); // 副作用!
return this.user;
}
}
computed
应是纯函数,无副作用。
✅ 正确做法:用 watch
+ async/await
js
watch: {
userId: 'fetchUser'
},
methods: {
async fetchUser(id) {
this.user = await api.getUser(id);
}
}
❌ 误区 2:用 watch
实现简单计算
js
// ❌ 浪费性能
watch: {
firstName() {
this.fullName = this.firstName + ' ' + this.lastName;
},
lastName() {
this.fullName = this.firstName + ' ' + this.lastName;
}
}
应使用
computed
。
💡 结语
"
computed
是你的数据加工厂,watch
是你的事件调度员。"
选择标准 | 使用 computed |
使用 watch |
---|---|---|
是否基于现有数据计算? | ✅ | ❌ |
是否需要缓存? | ✅ | ❌ |
是否涉及异步或副作用? | ❌ | ✅ |
是否昂贵操作? | ❌ | ✅ |
是否需要立即执行? | ❌ | ✅ |
记住:
- 优先使用
computed
,它是 Vue 响应式的灵魂; watch
是computed
无法胜任时的"补充武器"。