Vue3 的响应式系统是整个框架的灵魂,它让开发者能够在不显式调用更新的情况下自动响应数据变化。本文将带你深入阅读 Vue3 的核心响应式模块源码,重点讲解 Dep
、Link
、track
、trigger
等关键机制,并用通俗的语言串联其工作流程,让你真正理解 Vue3 响应式系统的运行原理。
一、响应式系统的设计思路
Vue3 的响应式系统基于 依赖收集(track) 与 派发更新(trigger) 两大过程:
- track:在读取响应式数据时记录依赖,建立「谁依赖了谁」的关系;
- trigger:当依赖的数据发生变化时,通知对应的副作用函数(effect)重新执行。
核心问题是:如何高效、准确地追踪依赖关系并在更新时精准触发?
Vue3 使用 Dep
(依赖容器)和 Link
(依赖连接)这两个类,配合 targetMap
(全局依赖映射表),构建出一个双向的依赖追踪结构。
二、Dep:依赖容器
Dep
是每个响应式属性的依赖中心,负责维护所有订阅它的副作用函数。
typescript
export class Dep {
version = 0
activeLink?: Link
subs?: Link
subsHead?: Link
sc: number = 0
constructor(public computed?: ComputedRefImpl | undefined) {}
}
主要属性:
- version:当前依赖的版本号,每次触发时递增;
- subs / subsHead:双向链表,记录所有订阅者(副作用函数);
- activeLink:当前活跃的依赖连接;
- sc:订阅者数量计数。
Dep 是响应式系统的"核心神经节点",它知道有哪些副作用依赖自己,也能在变化时精确通知它们。
三、Link:Dep 与 Effect 的连接纽带
Dep
与 Effect
(副作用函数)是多对多关系。
Link 就是这两者之间的桥梁,用于在它们之间建立和维护依赖。
typescript
export class Link {
version: number
nextDep?: Link
prevDep?: Link
nextSub?: Link
prevSub?: Link
constructor(public sub: Subscriber, public dep: Dep) {
this.version = dep.version
}
}
可以理解为:
-
每个
Link
同时存在于两条链表中:- 一条属于
Effect
,记录它依赖的所有Dep
; - 一条属于
Dep
,记录它的所有订阅者。
- 一条属于
-
当
Effect
执行时,Link
会根据访问的属性动态更新依赖关系。
这种 双向链表结构 能快速定位和清理依赖,避免重复依赖和内存泄漏。
四、track:依赖收集
track()
是读取响应式属性时调用的核心函数。它的任务是:
在读取属性时记录当前活跃的副作用函数(activeSub),让它与属性对应的 Dep 建立联系。
vbnet
export function track(target: object, type: TrackOpTypes, key: unknown): void {
if (shouldTrack && activeSub) {
let depsMap = targetMap.get(target)
if (!depsMap) {
targetMap.set(target, (depsMap = new Map()))
}
let dep = depsMap.get(key)
if (!dep) {
depsMap.set(key, (dep = new Dep()))
dep.map = depsMap
dep.key = key
}
dep.track({ target, type, key })
}
}
执行流程如下:
- 查找当前对象在
targetMap
中的依赖映射; - 如果没有就创建一个新的;
- 获取对应 key 的
Dep
; - 调用
dep.track()
将当前的activeSub
(副作用函数)与此属性关联。
简单来说,track()
就是在"我读取了这个属性"时,登记"我依赖它"。
五、trigger:派发更新
trigger()
是响应式系统的另一端,它在属性修改时触发依赖更新。
scss
export function trigger(target, type, key?, newValue?, oldValue?) {
const depsMap = targetMap.get(target)
if (!depsMap) {
globalVersion++
return
}
const run = (dep: Dep | undefined) => dep && dep.trigger()
if (type === TriggerOpTypes.CLEAR) {
depsMap.forEach(run)
} else {
const targetIsArray = isArray(target)
const isArrayIndex = targetIsArray && isIntegerKey(key)
if (targetIsArray && key === 'length') {
// 数组长度变化时触发相关依赖
const newLength = Number(newValue)
depsMap.forEach((dep, key) => {
if (key === 'length' || key >= newLength) run(dep)
})
} else {
run(depsMap.get(key))
if (isArrayIndex) run(depsMap.get(ARRAY_ITERATE_KEY))
switch (type) {
case TriggerOpTypes.ADD:
run(depsMap.get(ITERATE_KEY))
break
case TriggerOpTypes.DELETE:
run(depsMap.get(ITERATE_KEY))
break
}
}
}
}
核心思路:
- 修改属性 → 找到对应的
Dep
; - 调用
dep.trigger()
通知所有订阅者; - 依赖对应的
Effect
(或Computed
)重新执行。
Vue3 针对不同类型(对象、数组、Map、Set)做了细致优化,比如:
- 数组修改
length
会触发超出新长度的索引依赖; - Map 的
ADD
、DELETE
会额外触发迭代器依赖。
六、globalVersion 与批处理机制
每次响应式变更都会触发:
globalVersion++
它用于快速判断计算属性(computed
)是否需要重新计算。
此外,Vue3 在触发更新时通过 startBatch()
与 endBatch()
包裹通知过程,实现了批量触发、延迟执行,从而显著减少重复计算和性能浪费。
七、依赖结构图示
为了更直观地理解整个机制,可以把依赖关系抽象为如下结构:
rust
Reactive Target
└── key -> Dep
├── Link -> Effect A
├── Link -> Computed B
└── Link -> Effect C
track()
:在访问属性时建立这些箭头;trigger()
:当属性变化时,沿箭头依次通知所有订阅者。
八、总结
Vue3 的响应式系统在性能和结构上都做了精细设计:
- 使用
Dep
管理依赖; - 用
Link
构建高效的双向链表; - 借助
targetMap
构建全局依赖关系; - 通过
track
与trigger
完成依赖收集与派发更新; - 引入
globalVersion
与批处理优化机制,避免多余计算。
当你理解了这些底层逻辑,再回头看 Vue 的 reactive()
、computed()
、watch()
等 API,就会发现------它们都是基于这套机制自然生长出来的。
本文内容由人工智能生成,仅供学习与参考使用,请在实际应用中结合自身情况进行判断。