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

逐行解读

Vue3 的响应式系统是整个框架的灵魂,它让开发者能够在不显式调用更新的情况下自动响应数据变化。本文将带你深入阅读 Vue3 的核心响应式模块源码,重点讲解 DepLinktracktrigger 等关键机制,并用通俗的语言串联其工作流程,让你真正理解 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 的连接纽带

DepEffect(副作用函数)是多对多关系。
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 的 ADDDELETE 会额外触发迭代器依赖。

六、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 构建全局依赖关系;
  • 通过 tracktrigger 完成依赖收集与派发更新;
  • 引入 globalVersion 与批处理优化机制,避免多余计算。

当你理解了这些底层逻辑,再回头看 Vue 的 reactive()computed()watch() 等 API,就会发现------它们都是基于这套机制自然生长出来的。


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

相关推荐
前端大卫2 小时前
一个关于时区的线上问题
前端·javascript·vue.js
whltaoin3 小时前
中秋赏月互动页面:用前端技术演绎传统节日之美
前端·javascript·html·css3·中秋主题前端
IT派同学3 小时前
TableWiz诞生记:一个被表格合并逼疯的程序员如何自救
前端·vue.js
西洼工作室5 小时前
CSS高效开发三大方向
前端·css
昔人'5 小时前
css`font-variant-numeric: tabular-nums` 用来控制数字的样式。
前端·css
铅笔侠_小龙虾6 小时前
动手实现简单Vue.js ,探索Vue原理
前端·javascript·vue.js
哟哟耶耶8 小时前
Starting again-02
开发语言·前端·javascript
Apifox.8 小时前
Apifox 9 月更新| AI 生成接口测试用例、在线文档调试能力全面升级、内置更多 HTTP 状态码、支持将目录转换为模块
前端·人工智能·后端·http·ai·测试用例·postman
Kitasan Burakku8 小时前
Typescript return type
前端·javascript·typescript