Vue3 的响应式系统源码级 track 与 trigger 的依赖收集与触发更新详细流程解析
字数 1458 2025-11-20 01:01:16

Vue3 的响应式系统源码级 track 与 trigger 的依赖收集与触发更新详细流程解析

1. 核心概念:依赖收集与触发更新的基本逻辑

Vue3 的响应式系统基于 ProxyEffect 实现,核心流程分为两步:

  • track(依赖收集):在读取响应式数据时,将当前正在执行的副作用函数(effect)收集到依赖集合中。
  • trigger(触发更新):在修改响应式数据时,从依赖集合中取出所有关联的 effect 并重新执行,驱动视图更新。

2. 依赖收集(track)的详细步骤

步骤 1:建立响应式数据与 effect 的映射关系

Vue3 使用一个全局的 targetMap(类型为 WeakMap)存储所有响应式对象的依赖关系:

  • targetMap 的键是原始对象(target),值是一个 depsMap(类型为 Map)。
  • depsMap 的键是对象的属性名(key),值是一个 dep(类型为 Set),存储所有依赖该属性的 effect。

步骤 2:触发 track 的时机

当在 effect 中读取响应式数据时(例如 obj.foo),Proxy 的 get 拦截器会调用 track 函数:

// 简化版 track 逻辑
function track(target, key) {
  if (!activeEffect) return; // 无活跃 effect 则直接返回
  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 Set()));
  }
  dep.add(activeEffect); // 将当前 effect 添加到依赖集合
  activeEffect.deps.push(dep); // 同时记录 effect 对应的 dep(用于清理)
}

关键点

  • activeEffect 是全局变量,指向当前正在执行的副作用函数(通过 effect 函数注册)。
  • 每个 effect 会记录自己所有的依赖集合(deps),用于后续清理(避免遗留依赖)。

3. 触发更新(trigger)的详细步骤

步骤 1:修改数据时触发 Proxy 的 set 拦截器

当对响应式数据赋值(例如 obj.foo = 1)时,会调用 trigger 函数:

// 简化版 trigger 逻辑
function trigger(target, key, type) { // type 表示操作类型(SET、ADD、DELETE)
  const depsMap = targetMap.get(target);
  if (!depsMap) return;
  const effects = new Set();
  // 收集需要执行的 effect
  if (key !== undefined) {
    const dep = depsMap.get(key);
    dep && dep.forEach(effect => effects.add(effect));
  }
  // 处理数组的 length 变更或新增元素等特殊情况
  if (type === 'ADD' && Array.isArray(target)) {
    const lengthDep = depsMap.get('length');
    lengthDep && lengthDep.forEach(effect => effects.add(effect));
  }
  // 执行所有关联的 effect
  effects.forEach(effect => {
    if (effect.options.scheduler) {
      effect.options.scheduler(effect); // 支持调度器(如批量更新)
    } else {
      effect(); // 直接执行
    }
  });
}

关键点

  • 通过 targetkeytargetMap 中找到对应的 dep(依赖集合)。
  • 为避免无限循环(effect 内修改依赖数据),Vue3 使用调度器(scheduler)控制执行时机(例如放入微任务队列)。

4. 依赖清理与重新收集的机制

问题:为什么需要清理依赖?

考虑以下场景:

const obj = reactive({ flag: true, a: 1, b: 2 });
effect(() => {
  console.log(obj.flag ? obj.a : obj.b);
});
// 首次执行:依赖 obj.flag 和 obj.a
obj.flag = false;
// 第二次执行:应依赖 obj.flag 和 obj.b,但旧依赖(obj.a)需清理

解决方案:每次执行 effect 前清理旧依赖

在 effect 执行前,会先从其 deps 中移除所有关联的依赖集合,然后重新收集:

function cleanup(effect) {
  for (const dep of effect.deps) {
    dep.delete(effect); // 从所有依赖集合中移除该 effect
  }
  effect.deps.length = 0; // 清空 effect 的依赖记录
}

执行流程

  1. 触发 obj.flag = false → 执行 effect。
  2. 执行前调用 cleanup 清除旧依赖(obj.a 的 dep 移除该 effect)。
  3. 执行 effect 函数,读取 obj.flagobj.b → 重新收集依赖。

5. 总结:track 与 trigger 的协同流程

  1. 初始化
    • 通过 reactive() 创建 Proxy 代理对象。
    • 通过 effect() 注册副作用函数,执行时设置 activeEffect 为当前 effect。
  2. 依赖收集
    • 读取响应式数据 → Proxy 的 get 调用 track → 将 effect 存入对应 dep
  3. 触发更新
    • 修改响应式数据 → Proxy 的 set 调用 trigger → 从 dep 中取出 effect 执行。
  4. 依赖更新
    • effect 执行前清理旧依赖,执行时重新收集新依赖。

通过这一机制,Vue3 实现了精确的依赖跟踪高效的更新触发,避免不必要的重渲染,提升性能。

Vue3 的响应式系统源码级 track 与 trigger 的依赖收集与触发更新详细流程解析 1. 核心概念:依赖收集与触发更新的基本逻辑 Vue3 的响应式系统基于 Proxy 和 Effect 实现,核心流程分为两步: track(依赖收集) :在读取响应式数据时,将当前正在执行的副作用函数(effect)收集到依赖集合中。 trigger(触发更新) :在修改响应式数据时,从依赖集合中取出所有关联的 effect 并重新执行,驱动视图更新。 2. 依赖收集(track)的详细步骤 步骤 1:建立响应式数据与 effect 的映射关系 Vue3 使用一个全局的 targetMap (类型为 WeakMap )存储所有响应式对象的依赖关系: targetMap 的键是 原始对象(target) ,值是一个 depsMap (类型为 Map )。 depsMap 的键是对象的 属性名(key) ,值是一个 dep (类型为 Set ),存储所有依赖该属性的 effect。 步骤 2:触发 track 的时机 当在 effect 中读取响应式数据时(例如 obj.foo ),Proxy 的 get 拦截器会调用 track 函数: 关键点 : activeEffect 是全局变量,指向当前正在执行的副作用函数(通过 effect 函数注册)。 每个 effect 会记录自己所有的依赖集合( deps ),用于后续清理(避免遗留依赖)。 3. 触发更新(trigger)的详细步骤 步骤 1:修改数据时触发 Proxy 的 set 拦截器 当对响应式数据赋值(例如 obj.foo = 1 )时,会调用 trigger 函数: 关键点 : 通过 target 和 key 从 targetMap 中找到对应的 dep (依赖集合)。 为避免无限循环(effect 内修改依赖数据),Vue3 使用调度器(scheduler)控制执行时机(例如放入微任务队列)。 4. 依赖清理与重新收集的机制 问题:为什么需要清理依赖? 考虑以下场景: 解决方案:每次执行 effect 前清理旧依赖 在 effect 执行前,会先从其 deps 中移除所有关联的依赖集合,然后重新收集: 执行流程 : 触发 obj.flag = false → 执行 effect。 执行前调用 cleanup 清除旧依赖( obj.a 的 dep 移除该 effect)。 执行 effect 函数,读取 obj.flag 和 obj.b → 重新收集依赖。 5. 总结:track 与 trigger 的协同流程 初始化 : 通过 reactive() 创建 Proxy 代理对象。 通过 effect() 注册副作用函数,执行时设置 activeEffect 为当前 effect。 依赖收集 : 读取响应式数据 → Proxy 的 get 调用 track → 将 effect 存入对应 dep 。 触发更新 : 修改响应式数据 → Proxy 的 set 调用 trigger → 从 dep 中取出 effect 执行。 依赖更新 : effect 执行前清理旧依赖,执行时重新收集新依赖。 通过这一机制,Vue3 实现了 精确的依赖跟踪 和 高效的更新触发 ,避免不必要的重渲染,提升性能。