Vue3 的响应式系统源码级副作用函数(effect)调度与执行机制解析
字数 1141 2025-11-15 14:00:27
Vue3 的响应式系统源码级副作用函数(effect)调度与执行机制解析
题目描述
今天讲解 Vue3 响应式系统中副作用函数(effect)的调度与执行机制。我们将深入源码层面,分析 effect 如何被调度执行、调度器的核心逻辑、以及 Vue3 如何通过调度策略优化渲染性能。理解这一机制对处理异步更新、批量更新和自定义调度行为至关重要。
知识要点
- 副作用函数(effect)的基本结构
- 响应式依赖收集与触发更新的调度入口
- 调度器(scheduler)的实现与执行策略
- 批量更新(batching)和异步更新队列机制
- 渲染 effect 的特殊调度逻辑
逐步解析
1. 副作用函数(effect)的基本结构
Vue3 的 effect 是一个包装函数,用于跟踪响应式数据的变化:
class ReactiveEffect {
constructor(public fn, public scheduler?) {}
run() {
activeEffect = this
return this.fn()
}
// ... 其他方法
}
关键点:
- 每个 effect 包含原始函数
fn和可选的scheduler run方法执行前设置activeEffect用于依赖收集- 若存在
scheduler,触发更新时会优先调用调度器而非直接执行fn
2. 响应式更新的调度入口
当响应式数据变化时,触发依赖更新(源码节选):
function trigger(target, type, key) {
const depsMap = targetMap.get(target)
const dep = depsMap.get(key)
const effects = new Set()
dep.forEach(effect => effects.add(effect))
effects.forEach(effect => {
if (effect.scheduler) {
effect.scheduler() // 优先执行调度器
} else {
effect.run() // 直接执行
}
})
}
执行流程:
- 从全局
targetMap中找到依赖的 effect 集合 - 遍历 effect,检查是否存在
scheduler - 有调度器时调用
scheduler(),无调度器则直接执行run()
3. 调度器(scheduler)的核心逻辑
Vue3 的调度器通过队列机制管理 effect 执行:
const queue = new Set()
let isFlushing = false
function queueJob(job) {
queue.add(job)
if (!isFlushing) {
isFlushing = true
Promise.resolve().then(() => {
try {
queue.forEach(job => job())
} finally {
isFlushing = false
queue.clear()
}
})
}
}
调度策略说明:
- 使用
Set自动去重,避免同一 effect 重复入队 - 通过
isFlushing标记防重入,确保同一时间只有一个刷新任务 - 利用
Promise.resolve().then()将刷新推迟到微任务队列,实现异步批量更新
4. 渲染 effect 的调度绑定
组件渲染时创建带调度器的 effect(简化代码):
function setupRenderEffect(instance) {
instance.update = new ReactiveEffect(
() => instance.render(),
() => queueJob(instance.update) // 调度器绑定队列
)
}
关键设计:
- 组件渲染函数作为 effect 的
fn - 调度器将渲染任务加入队列,而非立即执行
- 确保同一组件的多次数据变化只触发一次渲染
5. 批量更新与异步执行的优势
调度机制带来的性能优化:
- 去重优化:同步修改多次数据,只会触发一次渲染
- 批处理:多个组件的更新合并为一次 DOM 操作
- 时机控制:在微任务中执行,避免阻塞主线程
总结
Vue3 的 effect 调度机制通过分离"依赖触发"和"任务执行",实现了高效的异步批量更新。调度器作为中间层,既支持默认的微任务队列策略,也允许自定义调度逻辑(如 watch 的 flush 选项)。这一设计是 Vue3 响应式系统高性能的关键保障。