Vue3 的监听工具变多了:watchwatchEffectwatchPostEffect,再加上 shallowWatchwatchSyncEffect……新手很容易绕晕。今天我们用“生活场景”把它们全部串起来,告诉你每个 API 的最佳使用时机,以及常见坑点

🎯 先搞清需求类型

需求 示例 推荐 API
精准监听某个响应式数据 监听表单某字段变化 watch
自动感知依赖,初次执行 根据多个响应式值实时计算 watchEffect
DOM 更新后再执行 要拿到最新渲染结果 watchPostEffect
需要同步执行 动画驱动、layout 读写 watchSyncEffect

🧱 watch 用法

const count = ref(0);

watch(count, (newVal, oldVal) => {
  console.log('count 变了', newVal, oldVal);
}, {
  immediate: true,
  deep: false,
});
  • immediate: 初次执行一次。
  • deep: 深度监听对象。
  • 支持监听多个源:watch([foo, bar], ([newFoo, newBar]) => {})

watch 监听 getter

watch(
  () => form.value.email,
  (newEmail) => {
    console.log('邮箱改变', newEmail);
  }
);
  • 适合监听对象内部的某个字段。
  • 避免 deep: true 带来的性能开销。

🧠 watchEffect 用法

watchEffect(() => {
  console.log('name or age 变化', form.value.name, form.value.age);
});
  • 自动收集在回调里访问的响应式依赖。
  • 初次执行一次。
  • 返回停止函数:
    const stop = watchEffect((onInvalidate) => {
      const token = startRequest();
      onInvalidate(() => cancelRequest(token));
    });
    // stop(); 在需要时停止监听
    

🧱 watchEffect vs watch

场景 watch watchEffect
依赖多但已知
依赖未知(动态)
想获取旧值
想立即执行 immediate: true 默认立即

🧩 watchPostEffect:DOM 更新后再执行

const el = ref<HTMLElement | null>(null);
const width = ref(0);

watchPostEffect(() => {
  width.value = el.value?.clientWidth ?? 0;
});
  • 类似于 React 的 useLayoutEffect vs useEffect 的区别。
  • watchPostEffect 在响应式更新同步提交 DOM 后执行。
  • 适合测量 DOM 或配合第三方库绘制。

⚙️ watchSyncEffect:同步执行

watchSyncEffect(() => {
  // 会在状态改变的同一 flush 阶段执行
  if (count.value > limit.value) {
    count.value = limit.value;
  }
});
  • 不等待下一轮微任务,直接同步执行。
  • 小心不要造成无限循环。

🧯 常见坑

坑点 现象 解决
watch 在组件销毁后仍触发 忘记在 setup 中返回 stop onBeforeUnmount 调用 stop
deep watch 卡顿 监听大对象 优先使用 getter + computed
watchEffect 里访问异步数据 无法获取旧值 使用 watch,或者在 effect 中手动存储
watchPostEffect 未生效 DOM 未渲染完成 确保模板中绑定了 ref

🔄 模拟实际场景

接口请求防抖

const keyword = ref('');

watch(keyword, (val, _, onCleanup) => {
  const timer = setTimeout(() => fetchList(val), 300);
  onCleanup(() => clearTimeout(timer));
});
  • onCleanup 在下一次执行前调用,类似 useEffect 的清理函数。

表单自动保存

watch(
  () => ({ ...form.value }),
  (newVal) => {
    localStorage.setItem('form', JSON.stringify(newVal));
  },
  { deep: true }
);

🏁 小练习

  1. 把项目里所有 deep watch 的场景改成 getter 写法。
  2. 写一个 useDebouncedWatch Hook 封装防抖逻辑。

下一篇,我们来研究 <script setup> + <script> 双模式怎么共存,怎么组织大组件的逻辑,如何在保留类型提示的同时不让代码看起来乱糟糟

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐