在 Vue 3 的响应式系统中,computed(计算属性)和 watch(侦听器)虽然都用于监听数据变化,但它们的设计初衷、底层机制和适用场景有着本质的区别。
简单来说:computed 是为了“得到一个新值”,而 watch 是为了“执行一段逻辑”。
以下是两者的核心区别深度解析:
1、核心定位与职责
| 特性 | Computed (计算属性) | Watch (侦听器) |
|---|---|---|
| 核心目的 | 派生状态:基于现有响应式数据,计算并返回一个新的响应式值。 | 副作用处理:监听数据变化,执行异步操作、DOM 更新或业务逻辑。 |
| 思维模式 | 声明式:告诉 Vue “B 是由 A 计算得来的”,Vue 自动维护关系。 | 命令式:告诉 Vue “当 A 变化时,去执行这个函数”。 |
| 返回值 | 必须有返回值,该返回值成为新的响应式数据。 | 无返回值(或返回值未被追踪),重点在于回调函数的执行。 |
| 典型场景 | 购物车总价、过滤后的列表、格式化显示字符串。 | API 请求、保存数据到 LocalStorage、手动操作 DOM、复杂的状态联动。 |
2、缓存机制与性能
Computed:有缓存(惰性求值)
computed会缓存计算结果。只有当其依赖的响应式数据发生变化时,才会重新执行计算函数。- 如果依赖未变,多次访问
computed属性会直接返回缓存值,不会重复执行函数体。 - 优势:适合高频访问或计算开销较大的场景(如大型数组过滤)。
Watch:无缓存
- 只要监听的数据源发生变化,
watch的回调函数就会立即执行,无论新值和旧值是否相同(除非手动判断)。 - 劣势:如果监听的数据频繁变化且回调逻辑沉重,可能导致性能问题。
3、异步处理能力
Computed:不支持异步
computed必须是同步函数。因为它需要立即返回一个值供模板或其他计算属性使用。如果在computed中使用async/await,返回的是一个 Promise 对象,而不是预期的数据,这会导致渲染错误。
Watch:天然支持异步
watch的回调函数可以是异步的。非常适合处理需要等待的场景,例如:当搜索关键词变化时,发起防抖后的 API 请求。
4、 触发时机与依赖追踪
Computed:隐式追踪 + 懒执行
- 隐式追踪:你不需要指定监听谁,
computed会自动追踪函数体内用到的所有响应式数据。 - 懒执行:只有在首次访问
.value或其依赖变化后再次访问时,才会执行计算。如果从未被使用,即使依赖变了也不会重新计算(直到下次访问)。
Watch:显式声明 + 主动执行
- 显式声明:你必须明确指定要监听哪个数据源(ref、reactive 或 getter 函数)。
- 主动执行:一旦数据源变化,回调立即执行。
- 额外选项:支持
immediate: true(初始化时立即执行一次)和deep: true(深度监听嵌套对象),这些是computed不具备的灵活配置。
5、 代码示例对比
场景:根据用户输入搜索数据
❌ 错误做法:用 Computed 处理异步副作用
// 报错或行为不符合预期,因为 computed 不能处理异步副作用
const searchResult = computed(async () => {
const res = await fetch(`/api/search?q=${keyword.value}`)
return res.json()
})
✅ 正确做法:用 Watch 处理异步请求
import { ref, watch } from 'vue'
const keyword = ref('')
const results = ref([])
// 监听 keyword 变化,执行异步请求
watch(keyword, async (newVal) => {
if (!newVal) return
const res = await fetch(`/api/search?q=${newVal}`)
results.value = await res.json()
}, { immediate: true }) // 可选:初始化时也执行
场景:展示格式化后的数据
✅ 正确做法:用 Computed 派生新值
import { ref, computed } from 'vue'
const firstName = ref('John')
const lastName = ref('Doe')
// 声明式地定义 fullName 是由 firstName 和 lastName 组成的
const fullName = computed(() => {
return `${firstName.value} ${lastName.value}`
})
// 模板中直接使用 {{ fullName }},具有缓存优势
6、选择指南:什么时候用哪个?
优先使用 computed
- 你需要根据一个或多个响应式数据计算出一个新值。
- 这个新值需要在模板中多次使用,或者作为其他计算属性的依赖。
- 计算逻辑是同步的。
- 你希望利用缓存来优化性能。
优先使用 watch
- 你需要在数据变化时执行副作用(Side Effects),如 API 请求、定时器、DOM 操作。
- 你需要执行异步逻辑。
- 你需要在数据变化时执行一些非计算性的操作,如打印日志、跳转路由。
- 你需要监听深层嵌套对象的变化(使用
deep: true)。 - 你需要在组件挂载时立即执行一次监听逻辑(使用
immediate: true)。
总结
- Computed 是“数据加工厂”,输入原材料,输出成品,有缓存,只关心结果。
- Watch 是“警报器”,听到响声就行动,无缓存,只关心过程(副作用)。
在实际开发中,如果一个功能既可以用 computed 也可以用 watch 实现(例如简单的数据同步),优先选择 computed,因为它更简洁、性能更好且更符合 Vue 的响应式哲学。只有当涉及副作用或异步操作时,才转向 watch。
