computed
和 watch
都是 Vue 中用于响应数据变化的机制,但它们在 使用方式、适用场景、实现原理 上有本质区别。一般从三个层面来深入分析:
1. 使用层面的区别
维度 | computed |
watch |
---|---|---|
用途 | 计算属性:基于已有数据派生出新值,用于模板渲染 | 观察者:监听数据变化后执行副作用操作(如异步请求、定时器、复杂逻辑) |
返回值 | 必须有返回值(用于视图) | 无返回值要求,通常用于执行操作 |
是否缓存 | ✅ 有缓存,依赖不变则不重新计算 | ❌ 无缓存,每次变化都会触发 |
适用场景 | 数据格式化、组合多个数据、条件判断表达式 | 异步操作、开销较大的操作、需要访问新旧值的场景 |
示例对比
vue
<template>
<div>
<p>全名: {{ fullName }}</p>
<p>搜索结果: {{ results }}</p>
</div>
</template>
<script>
export default {
data() {
return {
firstName: 'John',
lastName: 'Doe',
keyword: ''
}
},
// ✅ computed:用于模板中展示的派生值
computed: {
fullName() {
return this.firstName + ' ' + this.lastName
}
},
// ✅ watch:监听变化后执行异步操作
watch: {
keyword(newVal) {
if (newVal) {
this.debounceSearch(newVal) // 发起搜索请求
}
}
}
}
</script>
📌 总结:
computed
是"我要一个值",watch
是"我监听一件事并做点什么"。
2. 实现方式与响应式原理
要理解区别,必须深入 Vue 的响应式系统(以 Vue 3 为例,Vue 2 原理类似但实现不同)。
computed
的实现原理
computed
本质上是一个 惰性求值的响应式副作用函数(lazy effect)。- 在 Vue 3 中,
computed
是基于effect
和track/trigger
机制实现的。
js
import { computed } from 'vue'
const count = ref(1)
const doubled = computed(() => count.value * 2)
实现关键点:
computed
内部创建了一个computed effect
,它:- 第一次访问时执行 getter 函数,并收集依赖 (如
count
)。 - 标记为
dirty: true
表示需要重新计算。 - 只有当依赖变化时,才将
dirty
设为true
,下次访问时重新计算。
- 第一次访问时执行 getter 函数,并收集依赖 (如
- 具备缓存机制 :只要依赖未变,多次访问
doubled.value
不会重新执行 getter。
🔍 类比:
computed
就像一个"智能缓存函数",只有依赖变了才更新。
watch
的实现原理
watch
是一个 主动监听器,用于观察响应式数据的变化并执行回调。- 它基于
effect
的非懒加载版本(watchEffect
)实现。
js
watch(() => count.value, (newVal, oldVal) => {
console.log('count changed:', newVal)
})
实现关键点:
- 创建一个
watcher effect
,默认立即执行一次以建立依赖关系(immediate: true
可配置)。 - 每次依赖变化时,
trigger
会通知该 effect 重新执行回调。 - 不缓存结果:每次变化都会执行回调函数。
- 支持
deep
、immediate
、flush
等配置项。
🔍 类比:
watch
就像一个"监听器",只要数据变,我就执行。
3. 底层原理对比(Vue 响应式核心)
维度 | computed |
watch |
---|---|---|
响应式类型 | 派生状态(Derived State) | 副作用(Side Effect) |
依赖收集 | 是,在 getter 执行时收集 | 是,在 watch 的 source 函数中收集 |
触发机制 | 依赖变化 → 标记 dirty → 下次访问时重新计算 | 依赖变化 → 立即执行回调(可配置异步) |
缓存机制 | ✅ 有(基于 dirty flag) | ❌ 无(每次变化都触发) |
调度方式 | 惰性计算(lazy) | 同步或异步(可通过 flush: 'post' 推迟到 DOM 更新后) |
与模板关系 | 通常用于模板渲染,是视图的一部分 | 通常用于业务逻辑,解耦于视图 |
4. 高级理解:Vue 3 的 Reactive Effect
统一模型
在 Vue 3 中,computed
、watch
、watchEffect
都是基于统一的 effect
系统构建的:
ts
// 伪代码
function effect(fn, options) {
const effectFn = () => {
cleanup(effectFn)
activeEffect = effectFn
return fn()
}
if (!options.lazy) {
effectFn()
}
return effectFn
}
// computed = effect + lazy + 缓存
// watch = effect + 回调 + 新旧值对比 + 配置项
所以可以说:
computed
是"有缓存的、惰性的、返回值的 effect"
watch
是"带配置的、执行副作用的 effect"
5. 常见误区
问题 | 正确认知 |
---|---|
❌ 在 computed 中发起异步请求 |
✅ 应使用 watch 或 watchEffect |
❌ 在 watch 中返回值用于模板 |
✅ 应使用 computed |
❌ 认为 computed 每次都会执行 |
✅ 它有缓存,依赖不变不执行 |
❌ watch 只能监听 data |
✅ 可监听 ref 、reactive 、computed 、路径、函数返回值 |
总结
computed
是用来"算出一个值"的,它是响应式的、可缓存的、用于视图的派生状态;
watch
是用来"监听一个变化并执行操作"的,它是命令式的、无缓存的、用于处理副作用的观察者模式。
维度 | computed |
watch |
---|---|---|
本质 | 声明式、函数式思维 | 命令式、过程式思维 |
类比 | Excel 中的公式单元格 | 数据库的触发器(Trigger) |
推荐使用 | 模板中需要的计算值 | 异步操作、复杂逻辑、状态同步 |
💬 面试加分技巧
- 提到 Vue 3 的
setup
语法 中computed
和watch
的使用差异。 - 对比 Vuex 的
getters
(基于computed
)和 actions (类似watch
的副作用)。 - 提到 性能优化 :
computed
避免重复计算,watch
可配合debounce
防抖。