本页目录
调度器 scheduler
负责的任务
- 批量:同一事件循环里多次 set → 只更新一次。
- 去重:同一个 job(比如同一组件实例的更新)只入队一次。
- 阶段顺序:
- pre:渲染前的回调(watch(…, { flush: ‘pre’ })、onBeforeUpdate)。
- jobs:组件渲染更新 job(由渲染 effect 的 scheduler 入队)。
- post:渲染后的回调(watch(…, { flush: ‘post’ })、onUpdated、nextTick 之后看到的 DOM)。
- 时机:统一在 微任务(Promise.resolve().then(…))里 一次性 flush。
主要文件
- runtime-core/scheduler.ts
- queueJob:看 isFlushing / isFlushPending 如何决定“什么时候安排 flush”
- flushJobs:看 flushIndex 的推进与 排序/去重 逻辑
- queuePostFlushCb:看相同回调如何去重
- nextTick:返回 currentFlushPromise
- runtime-core/renderer.ts(或 createRenderer.ts)
- 组件的渲染 effect 被创建时,会传入一个 scheduler,其实现是 queueJob(componentUpdateJob)。
- runtime-core/apiWatch.ts
- doWatch 里给 watch/watchEffect 的 effect 配置 scheduler(根据 flush 选择入哪个阶段)。
- 找到 doWatch,看 const job = () => { … } 与 scheduler 如何把 job 放到 pre/post/queue
- reactivity/computed.ts
- computed 的内部 effect 有自己的 scheduler(不入队渲染,只是标记 dirty 并触发依赖)。
关键概念
- job:一次可执行任务(函数)。组件更新 job 通常带 job.id = instance.uid,用于稳定排序与去重。
- 三类队列:
- preFlushCbs(去重 Set/队列):渲染前回调。
- queue:组件更新等“主队列”。
- postFlushCbs:渲染后回调。
- 状态位:isFlushing、isFlushPending、flushIndex(当前执行到哪)、currentFlushPromise(nextTick 用)。
流程
js
state.xxx = newVal
→ trigger(target, key)
→ 对应 effect 被“触发”
→ 若 effect 有 scheduler,则调用 scheduler(job/runner)
- 组件更新:queueJob(updateComponentJob)
- watch 'pre':queuePreFlushCb(cb)
- watch 'post':queuePostFlushCb(cb)
→ 启动一次微任务 flush(queueFlush)
微任务阶段:
→ flushPreFlushCbs()
→ flushJobs()(按 job.id 排序、去重、依次执行渲染)
→ flushPostFlushCbs()
💡 案例分析
你会在控制台看到的大致顺序(点击「连续 +3」):
- [watch sync] …(同步,立即打印 3 次)
- 微任务 flush 开始
- 2.1 pre:[watch pre] …(只执行 1 次,取最终值)
- 2.2 jobs:子组件渲染更新(onBeforeUpdate → 重新 render)
- 2.3 post:onUpdated 等
- 再点击「msg 变化」:
- 先打印点击日志
- flush 时:先跑可能存在的 pre → 跑 jobs(渲染)→ 跑 post([watch post])
- await nextTick() 之后的日志在 post 之后
断点提示:
- 点「连续 +3」后,断点在 queueJob 可看到只入队一次的组件更新 job;
- flushJobs 里能看到 排序(按 job.id)和 去重;
- queuePostFlushCb 在修改 msg 时会被命中。
-
computed:内部 effect 的 scheduler 不入队,只做:
- 标记 dirty = true
- triggerRefValue(this) 通知依赖它的外部 effect/渲染在下一次访问再重新计算 → 因此它是“拉取式(lazy)”更新。
-
watch / watchEffect:由 doWatch 为 effect 设置 scheduler:
- flush: ‘pre’ → queuePreFlushCb
- flush: ‘post’ → queuePostFlushCb
- flush: ‘sync’ → 直接运行(无队列)
flags
ts
export enum SchedulerJobFlags {
QUEUED = 1 << 0, // 二进制: 0001 十进制: 1
PRE = 1 << 1, // 二进制: 0010 十进制: 2
ALLOW_RECURSE = 1 << 2, // 二进制: 0100 十进制: 4
DISPOSED = 1 << 3, // 二进制: 1000 十进制: 8
}
QUEUED
作用: 标识任务已经被添加到调度队列中
用途: 防止同一个任务被重复添加到队列
场景: 确保任务去重,避免重复调度
PRE
作用: 标识任务是一个预处理任务
用途: 预处理任务可以在任务执行之前执行,比如在任务执行之前更新状态,或者提前计算一些数据
场景:
- 指令的 beforeUpdate 钩子
- flush: “pre” 的 watch 回调
- 组件更新前的准备工作
ALLOW_RECURSE
作用: 允许任务递归执行
场景:
- 组件更新函数
- watch 回调
DISPOSED
作用: 标识任务已被销毁/释放
场景:
- 组件卸载时清理相关任务
- effect 作用域停止时