1. 引言
在 Vue3 开发中,computed(计算属性) 和 **watch(监听器)**是两个非常常用的响应式工具。很多初学者容易混淆它们:什么时候用 computed?什么时候用 watch?两者有什么区别和联系?本文用通俗的语言、完整的案例和清晰的对比,帮你彻底搞懂这两个属性。
2. 什么是 computed 计算属性
computed 的核心思想是:根据已有的响应式数据,派生出一个新的值。它就像一个"自动计算的公式",只要依赖的数据发生变化,计算属性就会自动重新计算,并且结果会被缓存。
通俗理解:computed 是"声明式"的,你告诉 Vue"我想要什么",Vue 自动帮你算好。它适合用在模板中展示经过加工的数据。
2.1 基础案例
下面是一个购物车总价的例子:
vue
<template>
<div>
<p>商品单价:{{ price }} 元</p>
<p>购买数量:{{ count }} 件</p>
<p>总价:{{ totalPrice }} 元</p>
<button @click="count++">增加数量</button>
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
const price = ref(100)
const count = ref(1)
// 计算属性:根据 price 和 count 自动派生总价
// 为什么这里可以写箭头函数呢?因为vue3 setup里没有this
const totalPrice = computed(() => {
return price.value * count.value
})
</script>
在这个例子中,totalPrice 不需要手动维护,只要 price 或 count 变化,它就会自动更新。而且只要依赖不变,多次访问 totalPrice 会直接返回缓存结果,不会重复计算。
2.2 computed 的特点
- 有缓存:依赖不变时,多次读取不会重复执行计算函数,性能更好。
- 只读(默认):计算属性默认只能读取,不能直接修改。如果确实需要修改,要使用带 getter 和 setter 的写法。
- 适合模板展示:把复杂的逻辑从模板中抽离出来,让模板更简洁。
下面来证明computed的特点
1. 有缓存,不重复执行计算函数
在模板上调用6次计算属性,每调用一次计算方法打印输出1,在控制台只输出了一次


下面是通过方法来实现,发现控制台打印了6次。


2. 只读,通过getter和setter修改

3. 什么是 watch 监听器
watch 的核心思想是:监听某个数据的变化,当它变化时执行一段副作用逻辑。它更像一个"哨兵",一旦发现目标变化,就触发你预先定义好的回调函数。和vue2的作用一致,但写法有区别。watch 可以监听以下几种情况:
- 监视 【ref】定义的【基本类型】数据,ref 需要手动开启深度监视
- 监视 【ref】定义的【对象类型】数据, ref 需要手动开启深度监视
- 监视【reactive】定义的【对象类型】数据,且默认是开启深度监视的, 并且无法关闭
- 监视 ref 或 reactive 定义的【对象类型】数据中的某个属性
- 监视上述的多个数据
什么时候开启深度监视 ?只有要监视对象内部属性时需要开启
通俗理解:watch 是"命令式"的,你告诉 Vue"当某个东西变了,去做某件事"。它适合处理异步操作、请求接口、操作 DOM 等有副作用的场景。
3.1 基础案例
情况一: 监视ref定义的【基本类型】数据,直接写数据名即可,不用写.value,但监视的是其value值的改变
当监视的值 >= 10, 停止监视

watch调用的返回值stopwatch,是用于停止监听的函数

情况二 :监视 ref 定义的【对象类型】数据: 直接写数据名,监视的是对象的【地址值】,若想监视对象内部的数据,要手动开启深度监视。
若修改整个 ref 定义的对象的属性,newValue 和 oldValue 都是新值,因为它们是同一个对象。
若修改整个 ref 定义的对象,newValue 是新值,oldValue 是旧值,因为不是同一个对象了

情况三: 监视【reactive】定义的【对象类型】数据,且默认是开启深度监视的, 并且无法关闭

情况四 :监视 ref 或 reactive 定义的【对象类型】数据中的某个属性
注意点如下:
1.若该属性值不是【对象类型】,需要写成函数形式。
2.若该属性值是依然是【对象类型】,可直接编,也可写成函数,不过建议写成函数。
1. 监听的属性值不是对象类型
监听person对象里的name属性,因为name不是对象,所以写成函数形式。

- 监听的属性值是对象类型

2.1 直接写对象的写法可以监听到对象属性里面的某个属性,但整个对象无法监听。

2.2 函数式可以监听整个对象地址,但对象里面的某个属性无法监听。

2.3 既要监听对象某个属性,又要监听整个对象。需要写成函数式,然后开启深度监视

情况五:监视上述的多个数据
需要写成数组的形式,并且数组里监听的对象要不要写成函数式看监听的是对象还是基本数据类型,基本数据类型需要写成函数式,对象类型推荐写成函数式。

可以看到newValue监听的是整个数组,不是数组中某个属性

3.2 watch 的特点
- 无缓存:每次数据变化都会触发回调,没有缓存机制。
- 适合副作用:异步请求、DOM 操作、日志记录等场景。
- 可监听多个数据:可以同时监听一个或多个响应式数据。
- 支持深度监听 :监听对象内部属性变化时,需要开启
deep选项。
4. watchEffect 监听器
watchEffect 和 watch同样是监听作用,区别在于watchEffect会立即执行一次函数,并且不需要指定监听哪些对象和属性,watchEffect会自动分析监听哪些,而watch要指定监听哪些属性和对象才行。
下面案例可以看到如果使用watch,需要先指定对哪些对象进行监听,而watchEffect不需要指定,watchEffect会自动帮我们分析监听哪些。
5. computed 与 watch 的对比
为了更直观地理解两者的区别,下面从多个维度进行对比。
5.1 核心区别对比表
| 对比维度 | computed 计算属性 | watch 监听器 |
|---|---|---|
| 核心思想 | 根据已有数据派生新值 | 监听数据变化并执行副作用 |
| 使用方式 | 声明式:告诉 Vue 想要什么 | 命令式:告诉 Vue 变化后做什么 |
| 是否有缓存 | 有缓存,依赖不变不重复计算 | 无缓存,每次变化都触发 |
| 返回值 | 返回一个派生值,可直接用于模板 | 不返回值,只执行回调逻辑 |
| 适用场景 | 模板中展示加工后的数据 | 异步请求、DOM 操作、复杂逻辑 |
| 是否支持异步 | 不支持(计算函数应为纯函数) | 支持,可在回调中执行异步操作 |
| 能否修改 | 默认只读,可配置 setter | 不涉及修改,只负责响应变化 |
5.2 联系与联系点
虽然两者用途不同,但它们也有紧密的联系:
- 都基于响应式系统:computed 和 watch 都依赖 Vue3 的响应式机制,能自动追踪数据依赖。
- 都能感知数据变化:两者都能在依赖数据变化时做出反应,只是反应方式不同。
- 可以配合使用:在实际项目中,经常先用 computed 派生数据,再用 watch 监听这个派生结果去执行副作用。
5.3 配合使用的案例
下面演示 computed 和 watch 如何协同工作:
vue
<script setup>
import { ref, computed, watch } from 'vue'
const price = ref(100)
const count = ref(1)
// computed:派生总价
const totalPrice = computed(() => price.value * count.value)
// watch:监听总价变化,执行副作用(比如上报埋点)
watch(totalPrice, (newTotal) => {
console.log('总价变化为:', newTotal)
// 这里可以发送埋点请求或执行其他副作用
})
</script>
在这个例子中,computed 负责"算",watch 负责"听"。当总价变化时,watch 自动触发埋点上报,两者各司其职、配合默契。
6. 如何选择:什么时候用哪个
选择原则可以总结为一句话:需要多次显示,用computed而不用方法,能用 computed 解决的,优先用 computed;需要执行副作用的,才用 watch。
- 如果只是想展示一个派生值(如总价、过滤后的列表、拼接后的字符串),用 computed。
- 如果需要在数据变化时执行一段逻辑(如请求接口、操作 DOM、保存草稿),用 watch。
- 如果既要派生数据,又要在派生结果变化时做额外处理,两者配合使用。
7. 总结
computed 和 watch 是 Vue3 响应式体系中的两大核心工具。computed 擅长"算 ",用声明式的方式从已有数据派生新值,自带缓存,适合模板展示;watch 擅长"听",用命令式的方式监听数据变化并执行副作用,适合异步和复杂逻辑。理解两者的区别和联系,能让你在开发中写出更清晰、更高效的代码。