← 返回题目列表

computed 和 watch 有什么区别?

高频 中等 第 2 / 27 题 更新于 2026/07/27
Vuecomputedwatch

简化版

computed 用于根据响应式数据派生新值,有缓存,依赖不变不会重新计算;watch 用于监听数据变化执行副作用,比如请求接口、写缓存、操作非 Vue 系统。能用 computed 表达派生数据时,不要用 watch。

详细版

computed:

const fullName = computed(() => first.value + last.value);

watch:

watch(keyword, async (value) => {
  results.value = await search(value);
});

区别:

  • computed 关注结果值。
  • watch 关注变化过程和副作用。
  • computed 有缓存。
  • watch 可拿到新旧值,可配置 immediatedeepflush

如果一个值可以由已有状态计算出来,优先 computed;如果变化后要做异步请求或外部操作,用 watch。

完整版教学

一、computed 是派生状态

派生状态指不需要单独存储,可以从已有状态算出来的值。比如总价、过滤后的列表、全名。

computed 的好处是缓存。只有依赖变了,才会重新计算。模板中多次使用 computed,不会重复跑 getter。

二、watch 是副作用入口

watch 不强调返回值,而强调“当某个数据变化时做一件事”。

watch(
  () => route.params.id,
  id => fetchDetail(id),
  { immediate: true }
);

这类请求、日志、同步 URL、调用第三方库,都属于副作用。

三、deep watch 的成本

deep: true 会深度监听对象内部变化,数据结构大时成本较高。很多时候更推荐监听具体字段:

watch(() => form.keyword, handler);

这样依赖更明确,也更可控。

四、watchEffect 的区别

watchEffect 会自动收集回调中用到的响应式依赖,立即执行一次。它适合依赖简单但不想手写来源的场景。

缺点是依赖不如 watch 明确,复杂业务中可读性可能下降。

五、面试追问与工程落地

computed 常见追问是“computed 能不能有 setter”。可以。可写 computed 适合把一个派生值反向拆回多个状态,比如表单里 fullName 拆成 firstName/lastName。但如果 setter 逻辑太复杂,说明状态模型可能需要重新设计。

watch 常见追问是 flush。默认 watch 回调在组件更新前后有特定调度时机;如果需要 DOM 更新后再执行,可以用 flush: 'post';如果要同步执行,可以用 flush: 'sync',但要谨慎,避免破坏批量更新优势。

工程里 watch 请求接口要处理竞态和清理。Vue 的 watch 回调可以使用 cleanup,在下一次回调执行前取消旧请求或忽略旧结果。否则快速输入搜索时,旧请求后返回可能覆盖新结果。

六、从依赖、调度和副作用推演选择

computed 内部是惰性求值的响应式 effect:没人读取时不必计算,读取时若依赖未变就返回缓存,依赖失效后标记为脏并在下次读取时重算。watch 则明确观察一个或多个来源,在值变化后按调度策略执行回调;它的产物不是一个派生值,而是请求、日志、DOM 操作等外部效果。

const price = ref(20)
const count = ref(3)
let runs = 0
const total = computed(() => {
  runs++
  return price.value * count.value
})
console.log(total.value, total.value, runs) // 60 60 1
能力computedwatchwatchEffect
依赖来源getter 自动收集显式 source同步执行期间自动收集
是否有返回值缓存
默认是否立即执行首次读取时
新旧值不以回调形式提供可提供不直接提供
典型用途派生状态精确副作用依赖与副作用写在一起

watch 默认回调通常发生在父组件更新后、所属组件 DOM 更新前;读取更新后的所属组件 DOM 应使用 flush: 'post'flush: 'sync' 会同步触发,失去批处理保护,若数组同步 push 1000 次就可能带来 1000 次回调,因此只适合非常轻量且变化频率低的来源。

异步 watch 要处理竞态。搜索词从 a 变为 ab 时,旧请求可能在新请求之后返回;应在清理回调中中止旧请求或标记旧结果失效。watchEffect 在异步回调中只会追踪第一个 await 之前同步读取的依赖,不能把 await 后的读取想当然地算进依赖。

判断口诀:需要“一个值”用 computed,需要“发生一件外部事情”用 watch;如果 watch 只是把 A 赋给可计算出的 B,通常是在重复存储派生状态。

七、常见误区与追问

  • 误区:computed 每次读取都会重新执行 getter。 依赖未变化时会复用缓存,只有失效并再次读取才重算。
  • 误区:watch 适合维护所有派生数据。 这会制造两份状态和同步时序,能够纯计算得到的结果应优先 computed。
  • 误区:deep watch 能免费观察大型对象所有变化。 深度遍历会访问嵌套属性,数据越大成本越高,应尽量监听具体来源。
  • 追问:watch 和 watchEffect 最大区别是什么? watch 显式指定来源并可比较新旧值;watchEffect 自动收集同步执行中读取的依赖并立即运行。
  • 追问:什么时候用 flush post? 回调必须读取所属组件更新后的 DOM 时使用,普通数据副作用通常无需改变默认时机。
  • 追问:flush sync 有什么风险? 它对每次同步变化立即触发,不参与常规批处理, 高频写入可能放大执行次数。
  • 追问:怎样避免搜索 watch 的请求竞态? 在下一次回调前清理旧副作用,使用 AbortController 或序号只接收最新结果。

八、加强记忆

computed 问“这个值怎么算”,watch 问“变化后要做什么”。派生数据用 computed,异步请求和外部副作用用 watch。

判断时先看结果能否由现有状态纯计算,再看是否需要清理异步副作用和选择回调时机。