调度本义是指控制一系列任务的执行顺序/编排规划。Vue3 的调度系统使其能够做到**“批量更新、不重复渲染、任务执行顺序可控”** 。
Vue 的调度系统 = 副作用执行顺序 + 去重 + 批量刷新
所有响应式变化,最终都不会“立刻执行”,而是被“调度”
一、Vue 为什么需要调度系统?
如果没有调度,会发生什么?
state.a++ state.b++ state.c++如果每次set都立即触发:
render() render() render()造成后果:
- 性能问题
- 顺序不可控
- DOM 不断更改,页面抖动
所以,Vue 的目标是:
state.a++ state.b++ state.c++ ↓ render() // 只执行一次(Scheduler 存在的意义)二、调度系统的数据结构
源码中的位置:packages/runtime-core/src/scheduler.ts
运行时(runtime)调度,对 effect 进行 “统一执行管理”。
调度系统不关心数据,只关心:
2.1 Job 的本质
type SchedulerJob = Function & { id?: number flags?: number }
- 没有 id,直接 push 进队列
- 有 id,按照顺序通过二分查找插入到合适的位置
Job ≈ effect.run / component update
2.2 核心队列
const queue: SchedulerJob[] = []所有待执行任务,都会进这个队列。
2.3 任务去重
const queued = new Set<SchedulerJob>()同一个 job,一个 flush 周期只会进队一次
三、调度入口:queueJob
export function queueJob(job: SchedulerJob) { if (!queued.has(job)) { queued.add(job) queue.push(job) queueFlush() } }- 去重(比如说
count++多次,最终的更新只需要一次) - 入队
- 触发 flush
四、flush:真正执行的地方
function queueFlush() { if (!isFlushing) { isFlushing = true resolvedPromise.then(flushJobs) } }Vue 的调度基于 microtask(Promise.then)
所以:
同步代码 → 全跑完 ↓ flushJobs(统一执行)五、flushJobs 的核心逻辑
function flushJobs() { try { // 批量执行 所有 job 集中执行一次 for (let i = 0; i < queue.length; i++) { const job = queue[i] job() } } finally { queue.length = 0 queued.clear() isFlushing = false } }六、组件更新的调度
每个组件都有一个 render effect
const effect = new ReactiveEffect(componentUpdateFn)scheduler 被设置为:
scheduler = () => queueJob(update) // UI 更新state change ↓ trigger ↓ component render effect.scheduler ↓ queueJob(update) ↓ flushJobs(异步更新) ↓ update() → render()七、computed / watch 在调度系统中的位置
7.1 computed 的 scheduler
scheduler = () => { dirty = true trigger(computed.dep) }computed 的任务调度不进 scheduler 队列(queueJob),只影响依赖它的 effect
7.2 watch 的 scheduler
scheduler = () => { queueJob(job) }watch 直接进入调度系统(具体进入哪个优先层级取决于 flush ,默认为 queueJob)
八、flush: pre / post / sync
Vue 的调度系统不是一个队列,而是三个层级
三种 flush 模式
8.1 pre 队列(默认 watch)
queuePreFlushCb(job)用于:
- watch
- beforeUpdate
8.2 post 队列(DOM 后)
queuePostFlushCb(job)用于:
- watch(flush: ‘post’)
- onMounted / onUpdated
8.3 执行顺序
flushPreFlushCbs ↓ flushJobs(组件更新) ↓ flushPostFlushCbs九、nextTick 的本质
export function nextTick(fn?) { return fn ? resolvedPromise.then(fn) : resolvedPromise }所以nextTick 本质是:等当前调度周期 flush 完(在原本调度系统 Promise.then(调度任务队列) 的后面又拼接了一个 .then(nextTick任务))
DOM 更新会在原本的调度系统中,所以 nextTick 在开发中一般用于获取最新的 DOM 。
十、简单示例
watch(state, () => console.log('watch')) state.count++ console.log('sync') nextTick(() => console.log('tick'))执行顺序:
sync watch render tick十一、为什么 Vue 不用 setTimeout / requestAnimationFrame?
Vue 的目标是:“同步代码结束后,立刻统一刷新”