Vue3架构设计——调度系统
调度本义是指控制一系列任务的执行顺序/编排规划。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 的目标是:“同步代码结束后,立刻统一刷新”

浙公网安备 33010602011771号