watch 和 watchEffect 有什么区别?

先理解原理和示例,再练习完整口述与追问。

watch 显式指定源,通常在源变化时运行;watchEffect 立即执行并自动追踪同步执行期间读取的依赖。异步函数在首次 await 之后读取的值不会因此被自动追踪。

watch 显式声明数据源,只在源按规则变化时调用回调,能取得新旧值并配置 immediate、deep 等;watchEffect 执行函数时自动追踪同步阶段读取的响应式依赖,初次立即运行。副作用内部读取的字段若只应作为附加数据、不应决定触发时机,watch 往往更可控。异步函数在第一个 await 后新读取的字段,不会被此前那轮 watchEffect 自动收集。

js
watch(() => props.userId, async (id, oldId, onCleanup) => {
  let stale = false
  onCleanup(() => { stale = true })
  const result = await loadUser(id)
  if (!stale) user.value = result
}, { immediate: true })

这是展示过期保护的片段;实际还要处理异常、loading 和取消。onCleanup 在异步等待前登记,旧一轮失效后不能继续覆盖新结果。

deep 不会自动产生深拷贝历史值,不能可靠用新旧对象引用比较所有字段变化。watchEffect 简洁不代表依赖越多越好,隐藏的请求触发条件会使排查困难。

watch 显式声明要观察的源,适合精确控制何时执行和比较新旧值;watchEffect 立即执行,并自动收集同步执行阶段读到的响应式依赖。watchEffect 若是 async,首次 await 之后读取的值不会自动成为这次追踪的依赖。数据来源清楚且需要控制触发时机时,我通常优先用 watch。

面试官可能继续问追问与完整解答展开收起

为什么 watch(obj.count, ...) 不符合预期?监听 reactive 对象与返回对象的 getter,在深层追踪上如何区分?

watch(obj.count, ...) 传入的是当前基本值,应传 () => obj.count 或 ref。直接 watch 一个 reactive 对象会形成隐式深度监听;watch 一个返回对象的 getter 默认重点看返回引用变化,要追踪其内部变化时再明确使用 deep。