Vue3 watch 家族:watch、watchEffect、watchPostEffect 全梳理
·
Vue3 的监听工具变多了:
watch、watchEffect、watchPostEffect,再加上shallowWatch、watchSyncEffect……新手很容易绕晕。今天我们用“生活场景”把它们全部串起来,告诉你每个 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 的
useLayoutEffectvsuseEffect的区别。 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 }
);
🏁 小练习
- 把项目里所有
deep watch的场景改成 getter 写法。 - 写一个
useDebouncedWatchHook 封装防抖逻辑。
下一篇,我们来研究 <script setup> + <script> 双模式怎么共存,怎么组织大组件的逻辑,如何在保留类型提示的同时不让代码看起来乱糟糟
更多推荐


所有评论(0)