响应式数据变化后,Vue 为什么不会立即更新 DOM?
Vue 的更新链路可以概括为:
组件首次渲染时,Vue 的 render effect 会读取 state.count,响应式系统记录“这个 effect 依赖 count”。当执行:
Proxy 的 set 会触发依赖该属性的 effect。组件更新不是直接在 set 内同步执行,而是交给 scheduler 调度。
如果每次赋值都立即重新渲染,组件会重复执行三次。Vue 会把组件更新任务放进队列,并通过任务去重让同一个组件在一个刷新周期内尽量只更新一次。
这样可以:
nextTick 与更新队列Vue 通常在当前同步代码执行完后,通过 Promise 微任务刷新队列:
nextTick 不是“让数据变成响应式”,也不是任意异步操作的等待器。它解决的是等待 Vue 完成本轮调度和 DOM 更新。
组件首次挂载时,render effect 负责创建 VNode 并完成 mount;后续更新时,它生成新的 VNode,renderer 根据类型、props、子节点和 key 进行 patch。
Vue 的 scheduler 不只是一个简单数组。不同任务会在不同阶段执行,常见概念包括:
watch / watchEffect 的 flush 选项会影响执行时机:
pre:组件更新前执行,默认常见选择;post:组件 DOM 更新后执行,适合读取更新后的 DOM;sync:依赖变化时立即执行,可能失去批处理收益,应谨慎使用。通常情况下,父组件因自身状态更新时会先执行父组件更新,再根据新的 VNode 更新子组件;子组件自己的响应式状态变化则可以独立触发子组件更新。
不要把“组件 render 执行”与“DOM 一定变化”混为一谈:render 可能重新执行,但 patch 会根据 VNode 差异决定是否真正修改 DOM。
通常不会,Vue 会把更新放入异步队列。需要读取新 DOM 时使用 nextTick。
nextTick 是固定延时不是。它等待的是 Vue 当前更新队列的刷新,不是 setTimeout 式的固定时间。
Vue 会尽量合并同一刷新周期的任务,但不同 flush 阶段、同步 watcher 或其他更新来源可能导致多次执行,不应把一次渲染当成绝对保证。
不会。长时间同步 JavaScript 仍然会阻塞主线程,调度器只能安排任务,不能自动拆分任意同步函数。
Vue 组件渲染本质上是一个 render effect。响应式数据变化后,Proxy 的 trigger 会通知依赖,但组件更新通常不会立即执行,而是进入 scheduler 队列。Vue 通过任务去重和异步刷新合并更新,组件 render 生成新 VNode,再由 patch 更新 DOM;
nextTick用于等待本轮 DOM 更新完成。