Vue3架构设计——调度系统

调度本义是指控制一系列任务的执行顺序/编排规划。Vue3 的调度系统使其能够做到**"批量更新、不重复渲染、任务执行顺序可控"** 。

Vue 的调度系统 = 副作用执行顺序 + 去重 + 批量刷新

所有响应式变化,最终都不会"立刻执行",而是被"调度"

一、Vue 为什么需要调度系统?

如果没有调度,会发生什么?

JavaScript 复制代码
state.a++
state.b++
state.c++

如果每次 set 都立即触发:

Plain 复制代码
render()
render()
render()

造成后果:

  • 性能问题
  • 顺序不可控
  • DOM 不断更改,页面抖动

所以,Vue 的目标是:

Plain 复制代码
state.a++
state.b++
state.c++
↓
render()  // 只执行一次(Scheduler 存在的意义)

二、调度系统的数据结构

源码中的位置:packages/runtime-core/src/scheduler.ts

运行时(runtime)调度,对 effect 进行 "统一执行管理"。

调度系统​不关心数据​,只关心:

2.1 Job 的本质

TypeScript 复制代码
type SchedulerJob = Function & {
  id?: number
  flags?: number
}
  • 没有 id,直接 push 进队列
  • 有 id,按照顺序通过二分查找插入到合适的位置

Job ≈ effect.run / component update

2.2 核心队列

TypeScript 复制代码
const queue: SchedulerJob[] = []

所有待执行任务,都会进这个队列。

2.3 任务去重

TypeScript 复制代码
const queued = new Set<SchedulerJob>()

同一个 job,一个 flush 周期只会进队一次

三、调度入口:queueJob

TypeScript 复制代码
export function queueJob(job: SchedulerJob) {
  if (!queued.has(job)) {
    queued.add(job)
    queue.push(job)
    queueFlush()
  }
}
  1. 去重(比如说 count++ 多次,最终的更新只需要一次)
  2. 入队
  3. 触发 flush

四、flush:真正执行的地方

TypeScript 复制代码
function queueFlush() {
  if (!isFlushing) {
    isFlushing = true
    resolvedPromise.then(flushJobs)
  }
}

Vue 的调度基于 microtask(Promise.then)

所以:

Plain 复制代码
同步代码 → 全跑完
↓
flushJobs(统一执行)

五、flushJobs 的核心逻辑

TypeScript 复制代码
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

TypeScript 复制代码
const effect = new ReactiveEffect(componentUpdateFn)

scheduler 被设置为:

TypeScript 复制代码
scheduler = () => queueJob(update) // UI 更新
Plain 复制代码
state change
 ↓
trigger
 ↓
component render effect.scheduler
 ↓
queueJob(update)
 ↓
flushJobs(异步更新)
 ↓
update() → render()

七、computed / watch 在调度系统中的位置

7.1 computed 的 scheduler

TypeScript 复制代码
scheduler = () => {
  dirty = true
  trigger(computed.dep)
}

computed 的任务调度不进 scheduler 队列(queueJob),只影响依赖它的 effect

7.2 watch 的 scheduler

TypeScript 复制代码
scheduler = () => {
  queueJob(job)
}

watch ​直接进入调度系统​(具体进入哪个优先层级取决于 flush ,默认为 queueJob)

八、flush: pre / post / sync

Vue 的调度系统 不是一个队列,而是三个层级

三种 flush 模式

8.1 pre 队列(默认 watch)

TypeScript 复制代码
queuePreFlushCb(job)

用于:

  • watch
  • beforeUpdate

8.2 post 队列(DOM 后)

TypeScript 复制代码
queuePostFlushCb(job)

用于:

  • watch(flush: 'post')
  • onMounted / onUpdated

8.3 执行顺序

Plain 复制代码
flushPreFlushCbs
 ↓
flushJobs(组件更新)
 ↓
flushPostFlushCbs

九、nextTick 的本质

TypeScript 复制代码
export function nextTick(fn?) {
  return fn
    ? resolvedPromise.then(fn)
    : resolvedPromise
}

所以 ​nextTick 本质是:等当前调度周期 flush 完(在原本调度系统 ​​Promise.then(调度任务队列)​​ ​ 的后面又拼接了一个 ​.then(nextTick任务)​)

DOM 更新会在原本的调度系统中,所以 nextTick 在开发中一般用于获取最新的 DOM 。

十、简单示例

JavaScript 复制代码
watch(state, () => console.log('watch'))

state.count++

console.log('sync')

nextTick(() => console.log('tick'))

执行顺序:

Plain 复制代码
sync
watch
render
tick

十一、为什么 Vue 不用 setTimeout / requestAnimationFrame?

Vue 的目标是:"同步代码结束后,立刻统一刷新"

相关推荐
半个落月42 分钟前
LangSmith 全链路观测(下):用 Dataset 与 LLM Judge 量化评估 RA
javascript·langchain
半个落月1 小时前
LangSmith 全链路观测(上):用 LangGraph 与 Milvus 构建可追踪的 RAG 客服
javascript·langchain
Java后端的Ai之路2 小时前
06-React-Props
前端·react.js·前端框架
半个落月2 小时前
LangChain.js Agent 中间件实战:生命周期、动态工具与文件权限
javascript·langchain
Flynt2 小时前
开源"间谍卫星"说数据全是真的,我把它的免key数据源挨个试了一遍
前端·开源·数据可视化
Csvn3 小时前
渲染管线
前端
程序员大阳3 小时前
副队长CSS教程(33)–圆角与透明
前端·css·透明·圆角
志尊宝3 小时前
Vue3 零基础每日笔记(082):文件上传实战——el-upload 校验、自定义上传、头像预览
前端·javascript·vue.js·笔记·#javascript·#vue.js
鬼手点金4 小时前
opencode-rules 插件
java·开发语言·前端·人工智能·chrome·git·github
柳杉4 小时前
借助 Codex 和可视化工作流,从调研到完整实现,3D 大屏竟然可以这么简单
前端·ai编程·数据可视化