先记住这个答案
默认的 watchEffect 会在创建时执行效果函数,以产生初始副作用并建立实际读取的依赖;computed 返回可读取的派生引用,没有消费者读取时无需立即计算,读取时再按依赖状态求值并复用缓存。它们的差别来自用途:副作用负责做事,计算属性负责提供值。若 watchEffect 使用 post 调度,首次执行也会被推迟,不能把默认行为说成所有配置下都同步执行。
- 默认 watchEffect 创建后开始执行
- computed 为消费者提供按需求取的派生值
- 缓存计算函数应保持纯粹
为什么要先执行才能知道依赖
watchEffect 的依赖来自函数实际运行时的同步读取,框架不能通过静态扫描猜测哪个条件分支会执行。第一次执行既让业务获得初始效果,也让响应式系统知道随后应订阅哪些数据变化。
computed 也需要运行 getter 才知道依赖,但它可以等到值被使用时再做这件事。例如一个折叠面板里的复杂统计从未被读取,就没有必要为了创建引用先完成全部计算。
通过读取位置观察求值行为
下例用计数变量观察 getter 是否运行,这是隔离实验的检测手段。创建 computed 后计数仍为零,第一次读取才计算;依赖未变化时再次读取复用结果,不会因为又访问一次 value 就完整重算。
同时创建的默认 watchEffect 会留下初始记录。示例把两个机制放在相同源上,只比较首次运行与读取关系,不把它们的执行次数当成实际项目里可依赖的性能承诺。
import { ref, computed, watchEffect, nextTick } from 'vue'
const count = ref(2)
let getterRuns = 0
const doubled = computed(() => {
getterRuns++ // 仅用于本次实验观察
return count.value * 2
})
const createdRuns = getterRuns
const effects: number[] = []
const stop = watchEffect(() => { effects.push(count.value) })
const first = doubled.value
const second = doubled.value
const beforeChangeRuns = getterRuns
count.value = 3
await nextTick()
const beforeReadRuns = getterRuns
const latest = doubled.value
stop()这个没有其他消费者的实验中,两次初始读取只计算一次,依赖改变后直到再次读取才得到六。效果记录则包含二和三;真实模板若正在读取 computed,本身就会成为推动求值的消费者。
职责决定哪种逻辑放在哪里
展示总价、筛选列表和拼接标签通常是由输入确定的派生值,放入 computed 可以避免额外维护同步状态。写本地存储、调用外部接口和控制第三方实例属于副作用,需要明确启动与清理流程。
不要把异步请求写进 computed 后期望框架等待它并管理失败。getter 返回 Promise 也只是一个计算结果,不会自动变成请求状态机;需要异步状态时,用明确的监听或事件流程管理数据与错误。
容易答错的地方
- 认为依赖一变 computed 必然立刻重算
- 没有消费者读取的情况下,依赖变化可以先使结果需要重新评估,而不立即执行 getter。分析性能应结合谁读取了派生值及读取时机,不能只数源赋值次数。
- 在 computed 中做必须执行的工作
- 派生值可能没有被读取,也可能因消费者变化而在不同时间求值,因此不适合承担必须发生的请求、写入或日志流程。把必须执行的副作用放在明确的事件或侦听器中,并管理清理。
面试官还会怎么问?
为什么普通 watch 默认不会执行回调?
watch 可以先执行源 getter 建立观察并保存初值,而不立即运行副作用回调,二者本来就是分开的。需要初始业务执行时配置 immediate,这与 watchEffect 合并读取和副作用的模型不同。
watchPostEffect 创建时也同步执行吗?
不会按默认 pre 效果的首次同步方式执行。它使用 post 调度,初次效果也会延后到相应刷新阶段。面试回答需要先限定默认 watchEffect,避免将某种配置当成所有效果的共同保证。
getter 内只有 Date.now 为什么不会持续更新?
时间读取本身不是 Vue 响应式依赖,框架不会因为真实时间流逝自动使计算属性更新。若要持续展示时间,需要由定时更新的响应式源驱动,并在所有者结束时清理定时器。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。