标准答案

  1. computed 是计算属性,返回一个由响应式数据派生出来的值。它有缓存,依赖不变时重复读取不会重新计算,适合展示金额、过滤列表、组合标题、按钮禁用状态这类派生状态。
  2. watch 监听一个明确的数据源,可以拿到新值和旧值,适合请求接口、写入缓存、同步 URL、上报埋点这类副作用。
  3. watchEffect 会立即执行传入函数,并自动追踪同步执行过程中读取到的响应式依赖。依赖变化时重新执行,适合依赖来源较多、但副作用逻辑比较短的场景。
  4. computed 应该保持纯计算,不要在里面发请求或修改外部状态。watch 和 watchEffect 可以做副作用,但要处理清理逻辑、竞态和触发时机。
  5. 如果能写成一个派生值,优先用 computed;如果要响应变化后做一件事,用 watch;如果依赖很多又不想手动列来源,可以考虑 watchEffect。

题目解析

这三个 API 的核心区别是“返回值”和“副作用”。computed 本质上是响应式派生值,watch / watchEffect 主要用于观察变化后执行副作用。

watch 的数据源更清楚,适合需要比较新旧值、控制 immediate、deep、flush 的场景。watchEffect 写起来短,但依赖是自动收集的,复杂逻辑里可读性可能下降。

flush 会影响回调执行时机。默认 pre 会在组件 DOM 更新前执行,post 更适合读取更新后的 DOM,sync 会同步触发,使用时要避免频繁更新造成性能问题。

代码示例

这个例子展示 computed 负责派生展示值,watch 负责搜索请求,watchEffect 负责自动跟随依赖输出状态。

Vue
<script setup lang="ts">
import { computed, ref, watch, watchEffect } from 'vue'

const keyword = ref('')
const page = ref(1)
const price = ref(100)
const count = ref(2)

const totalPrice = computed(() => price.value * count.value)

watch(
  [keyword, page],
  ([nextKeyword, nextPage], [oldKeyword]) => {
    const requestPage = nextKeyword !== oldKeyword ? 1 : nextPage

    if (nextKeyword !== oldKeyword) {
      page.value = 1
    }

    fetchProducts(nextKeyword, requestPage)
  },
  { immediate: true }
)

watchEffect(() => {
  console.log('当前搜索条件:', keyword.value, page.value)
})

function fetchProducts(keyword: string, page: number) {
  console.log('request', { keyword, page })
}
</script>

常见误区

  • 把 computed 当 watch 用,在 computed 里发请求、改状态或写 localStorage。
  • watchEffect 里写太多逻辑,依赖来源变得不清楚,后续很难判断为什么重新执行。
  • 需要旧值时使用 watchEffect。watchEffect 不直接提供 oldValue,需要旧值就用 watch。

作者信息