Computed 与 Watch 对比
Vue 中 computed 和 watch 的区别、原理及使用场景
问题
Vue 中 computed 和 watch 有什么区别?各自的原理是什么?分别适用于哪些场景?
解答
基本区别
| 特性 | computed | watch |
|---|---|---|
| 返回值 | 必须有返回值 | 不需要返回值 |
| 缓存 | 有缓存,依赖不变不重新计算 | 无缓存,每次变化都执行 |
| 异步 | 不支持异步 | 支持异步操作 |
| 用途 | 派生数据 | 监听变化执行副作用 |
computed 示例
import { ref, computed } from 'vue'
const firstName = ref('张')
const lastName = ref('三')
// computed 会缓存结果,只有依赖变化时才重新计算
const fullName = computed(() => {
console.log('computed 执行') // 依赖不变时不会打印
return firstName.value + lastName.value
})
// 多次访问 fullName.value,只会计算一次
console.log(fullName.value) // 张三
console.log(fullName.value) // 张三(使用缓存,不重新计算)
watch 示例
import { ref, watch } from 'vue'
const searchText = ref('')
// watch 用于执行副作用,如异步请求
watch(searchText, async (newVal, oldVal) => {
if (newVal) {
// 支持异步操作
const result = await fetchSearchResult(newVal)
console.log('搜索结果:', result)
}
}, {
immediate: false, // 是否立即执行
deep: false // 是否深度监听
})
实现原理
computed 原理
// 简化版 computed 实现
function computed(getter) {
let value
let dirty = true // 标记是否需要重新计算
// 创建响应式 effect
const effect = new ReactiveEffect(getter, () => {
// 依赖变化时,标记为脏数据,但不立即计算
dirty = true
})
return {
get value() {
// 只有脏数据才重新计算(缓存机制)
if (dirty) {
value = effect.run()
dirty = false
}
return value
}
}
}
watch 原理
// 简化版 watch 实现
function watch(source, callback, options = {}) {
let oldValue
// 获取监听的值
const getter = () => source.value
// 调度器:值变化时执行回调
const scheduler = () => {
const newValue = effect.run()
callback(newValue, oldValue)
oldValue = newValue
}
const effect = new ReactiveEffect(getter, scheduler)
// immediate: 立即执行一次
if (options.immediate) {
scheduler()
} else {
oldValue = effect.run()
}
}
使用场景对比
import { ref, computed, watch } from 'vue'
// ✅ computed:根据已有数据计算新数据
const price = ref(100)
const quantity = ref(2)
const total = computed(() => price.value * quantity.value)
// ✅ computed:过滤/排序列表
const list = ref([3, 1, 2])
const sortedList = computed(() => [...list.value].sort())
// ✅ watch:执行异步操作
const userId = ref(1)
watch(userId, async (id) => {
const user = await fetchUser(id)
// 更新用户信息...
})
// ✅ watch:执行 DOM 操作或其他副作用
const count = ref(0)
watch(count, (val) => {
document.title = `计数: ${val}`
})
// ❌ 错误用法:用 watch 做 computed 的事
const a = ref(1)
const b = ref(0)
// 不推荐
watch(a, (val) => {
b.value = val * 2
})
// 推荐
const b = computed(() => a.value * 2)
watchEffect
import { ref, watchEffect } from 'vue'
const count = ref(0)
// watchEffect:自动收集依赖,立即执行
watchEffect(() => {
console.log('count:', count.value)
// 自动追踪 count,变化时重新执行
})
// 对比 watch:需要显式指定监听源
watch(count, (val) => {
console.log('count:', val)
})
关键点
- computed 有缓存:依赖不变时返回缓存值,适合计算派生数据
- watch 无缓存:每次变化都执行回调,适合执行副作用(异步请求、DOM 操作)
- computed 必须同步:getter 函数不能是异步的
- watch 支持配置:
immediate立即执行,deep深度监听 - 优先用 computed:能用 computed 解决的不要用 watch,代码更简洁、性能更好
目录