在 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无法胜任时的"补充武器"。