Vue3 响应式核心源码全解析:Dep、Link 与 track/trigger 完整执行机制详解

逐行解读

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 })
  }
}

执行流程如下:

  1. 查找当前对象在 targetMap 中的依赖映射;
  2. 如果没有就创建一个新的;
  3. 获取对应 key 的 Dep;
  4. 调用 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,就会发现------它们都是基于这套机制自然生长出来的。


本文内容由人工智能生成,仅供学习与参考使用,请在实际应用中结合自身情况进行判断。

相关推荐
Seraphina361 小时前
DVWA(SQL注入-low,medium,XSS反射-low)
前端·数据库·笔记·sql·网络安全·web·xss
Access开发易登软件1 小时前
Access 导入 Markdown 表格:把文档里的数据写进表
前端·html·vba·markdown·access开发
扶风ff2 小时前
求职笔试资料太散?用练题簿在线刷题,按岗位整理练习清单
前端·学习·小程序
浪浪山_大橙子2 小时前
公司里的 AI,终于不只会聊天:我用 GPT‑6 把企业工作伙伴开源了
前端·后端·面试
琹箐2 小时前
命令行调用接口
前端
可乐鸡翅yeah_2 小时前
AES‑128 加密 M3U8,IV 初始化向量新手容易踩坑
前端·网络·数据库·ffmpeg·音视频·m3u8在线
MetWeave3 小时前
机场天气的"广播系统":三种报文一次讲明白
前端
xieter3 小时前
【技术精选】深入浅出现代前端渲染与性能优化实践指南 (2026-10-04)
前端·javascript
计算机魔术师3 小时前
Anthropic 密会宗教领袖谈 Claude 意识,OpenAI 为什么急眼
前端
xieter3 小时前
【硬核实战】TypeScript Compiler API: Preserving Child Node Narrowing in Reusable Type Guards 🔧 (2026-10-03)
前端·javascript