适用读者:被"
watch没触发"坑过的人。
前置:S14-B01。
实测环境:Vue 3.5 / Node 22.20(无 DOM,纯响应式系统,2026-11-05)。
覆盖旧文:1489(44) 1490(50) 1491(25) = 119 点。
覆盖扩展:E-S14-003(Proxy 依赖收集)、E-S14-004(watch 异步批处理)、E-S14-006(事件循环)。
上一篇:S14-B02 · 模板语法:插值、绑定、事件、条件、循环
下一篇:S14-B04 · 样式方案。
1. 一句话结论
Vue 3 用 Proxy 拦截读写:读的时候收集依赖、写的时候通知更新。effect 同步执行,watch 默认异步批处理——这是"我改了值但回调没跑"的根因。
2. 实测:effect 同步、watch 异步
const n = ref(1)
watch(n, (nv, ov) => log.push(`watch: ${ov} → ${nv}`))
effect(() => log.push(`effect: n=${n.value}`))
n.value = 2
console.log(log) // 同步打印
实测输出:
同步打印时 log = ["effect: n=1","effect: n=2"]
微任务后 log = ["effect: n=1","effect: n=2","watch: 1 → 2"]
| 观察 | 结论 |
|---|---|
| 同步打印时只有 effect | effect 同步跑 |
watch 在微任务后出现 |
watch 异步(默认 pre flush) |
3. 实测:连续改值,watch 只触发一次
n.value = 3; n.value = 4
await Promise.resolve()
实测:
连续两次修改后 log = [...,"effect: n=3","effect: n=4","watch: 2 → 4"]
| 观察 | 结论 |
|---|---|
effect 跑了 两次(3、4) |
effect 是同步的 |
watch 只出现 一次(2 → 4) |
批处理:只保留最后一次 |
"这是 bug 吗":不是——watch 的设计目标就是"状态稳定后再响应"。要每次变更都触发 → flush: 'sync'。
4. ref / reactive / computed 的边界
| API | 适用 | 注意 |
|---|---|---|
ref(x) |
任意值(含基本类型) | 模板里自动解包,JS 里要 .value |
reactive(obj) |
对象/数组 | 解构会丢失响应性 |
computed(fn) |
派生值 | 有缓存,依赖不变不重算 |
watch(src, cb) |
副作用 | 异步批处理 |
watchEffect(fn) |
自动收集依赖 | 同步执行 |
"为什么 reactive 解构会失效":解构出来的只是普通值,丢失了 Proxy 包装。
5. 事件循环:理解异步的地基(E-S14-006)
实测(Node 22,CJS):
1 sync 开始
2 sync 结束
3 nextTick
4 promise.then
5 queueMicrotask
6 setTimeout(0)
7 setImmediate
实测(Node 22,ESM):
1 sync 开始
2 sync 结束
4 promise.then
5 queueMicrotask
3 nextTick ← 与 CJS 顺序不同
6 setTimeout(0)
7 setImmediate
结论:微任务(Promise / queueMicrotask)先于宏任务(setTimeout / setImmediate);而 nextTick 与 Promise 的相对顺序在 CJS/ESM 下不同(实测复现两次)——业务代码不要依赖这两者的相对顺序。
watch 的"微任务后"就落在这一层:改值 → 微任务刷新队列 → 回调执行。
6. 五个误区
| 误区 | 真相 |
|---|---|
"watch 改完就同步触发" |
默认异步批处理(实测) |
"computed 每次都重算" |
有缓存,依赖不变不重算 |
"reactive 解构仍响应" |
丢响应性 |
"ref 在 JS 里不用 .value" |
需要(模板里才自动解包) |
"nextTick 与 Promise 顺序固定" |
CJS/ESM 不同(实测) |
7. 自检
effect与watch的执行时机差异?- 连续两次改值,为什么
watch只触发一次?怎么让它每次都触发? ref与reactive的适用差异?- 微任务与宏任务谁先?
- 为什么不该依赖
nextTick与 Promise 的顺序?
8. 下一篇
S14-B04 · 样式方案:Tailwind 与 Animate.css
响应式讲完,下一篇讲样式:原子化 CSS 到底解决了什么。
本篇属于系列《S14 · 前端与可视化》。旧文来源:A-1489/1490/1491;扩展点 E-S14-003/004/006。effect/watch 时序与事件循环为 Vue 3.5 + Node 22 实测。