Vue3 的响应式系统源码级依赖收集与触发更新流程解析
字数 1445 2025-11-15 03:50:46

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

题目描述
请详细解析 Vue3 的响应式系统在源码层面如何实现依赖收集(track)和触发更新(trigger),包括核心数据结构的交互逻辑、执行时机以及异步调度细节。


1. 响应式系统的核心数据结构

Vue3 的响应式系统基于 Proxy 和 Reflect 实现,核心依赖三个数据结构:

  • TargetMap:类型为 WeakMap<Target, KeyToDepMap>,用于存储所有响应式对象与其依赖的映射。
  • KeyToDepMap:类型为 Map<Key, Dep>,存储响应式对象的每个属性对应的依赖集合。
  • Dep:类型为 Set<ReactiveEffect>,存储与属性相关的所有副作用函数(effect)。
// 源码简化结构  
type TargetMap = WeakMap<object, KeyToDepMap>;  
type KeyToDepMap = Map<string | symbol, Dep>;  
type Dep = Set<ReactiveEffect>;  

2. 依赖收集(track)流程

当访问响应式对象的属性时,Proxy 的 get 拦截器会触发 track 函数:

步骤分解

  1. 检查当前活跃的 effect

    • 只有处于 activeEffect(当前正在执行的副作用函数)时才会收集依赖。
    • activeEffectnull(例如在非 effect 上下文中访问属性),则直接返回。
  2. 查找或创建依赖映射

    • targetMap 中查找当前对象(target)对应的 depsMap,若不存在则创建新的 Map
    • depsMap 中查找当前属性(key)对应的 dep(依赖集合),若不存在则创建新的 Set
  3. 建立双向关联

    • activeEffect 添加到 dep 中(表示属性依赖此 effect)。
    • 同时将 dep 添加到 activeEffect.deps 数组中(用于后续清理依赖)。
// 简化版 track 逻辑  
function track(target: object, key: string | symbol) {  
  if (!activeEffect) return;  
  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);  
  activeEffect.deps.push(dep); // 记录 effect 的所有依赖  
}  

3. 触发更新(trigger)流程

当修改响应式对象的属性时,Proxy 的 set 拦截器会触发 trigger 函数:

步骤分解

  1. 查找依赖集合

    • targetMap 中获取 target 对应的 depsMap,若不存在则直接返回(说明从未被依赖)。
    • depsMap 中获取 key 对应的 dep(依赖集合)。
  2. 优化重复 effect

    • 使用 new Set() 合并 dep 与其他可能相关的依赖(如数组的 length 变更),避免重复执行同一个 effect。
  3. 调度执行 effect

    • 遍历依赖集合,若 effect 配置了 scheduler(如组件更新器),则交给调度器处理(异步批量更新)。
    • 否则直接同步执行 effect.run()
// 简化版 trigger 逻辑  
function trigger(target: object, key: string | symbol) {  
  const depsMap = targetMap.get(target);  
  if (!depsMap) return;  
  const effects = new Set<ReactiveEffect>();  
  const addEffects = (dep: Dep) => {  
    dep && dep.forEach(effect => effects.add(effect));  
  };  
  addEffects(depsMap.get(key));  
  // 处理数组长度变更等特殊情况  
  if (key === 'length' && isArray(target)) {  
    depsMap.forEach((dep, k) => {  
      if (k === 'length' || Number(k) >= (value as number)) {  
        addEffects(dep);  
      }  
    });  
  }  
  effects.forEach(effect => {  
    if (effect.options.scheduler) {  
      effect.options.scheduler(effect); // 进入异步调度队列  
    } else {  
      effect.run(); // 直接执行  
    }  
  });  
}  

4. 异步调度与批量更新

Vue3 通过 scheduler 实现异步批量更新,避免重复渲染:

  • 组件更新 effectscheduler 会将更新任务推入队列,并通过 nextTick 批量执行。
  • 示例:多次修改响应式数据时,仅触发一次组件更新。
// 调度器逻辑(简化版)  
const queue: ReactiveEffect[] = [];  
let isFlushing = false;  
function scheduler(effect: ReactiveEffect) {  
  if (!queue.includes(effect)) {  
    queue.push(effect);  
  }  
  if (!isFlushing) {  
    isFlushing = true;  
    Promise.resolve().then(() => {  
      while (queue.length) {  
        queue.shift()!.run(); // 批量执行更新  
      }  
      isFlushing = false;  
    });  
  }  
}  

5. 依赖清理机制

Effect 重新执行前会清理旧依赖,避免无效依赖残留:

function cleanup(effect: ReactiveEffect) {  
  effect.deps.forEach(dep => dep.delete(effect)); // 从所有依赖集合中移除  
  effect.deps.length = 0; // 清空依赖数组  
}  

总结
Vue3 的响应式系统通过 track 建立属性与 effect 的关联,通过 trigger 按需触发更新,结合调度器实现异步批量优化。源码层级的细节包括依赖集合的嵌套存储、双向关联的维护以及异步任务队列的管理,共同保证了响应式数据的高效更新。

Vue3 的响应式系统源码级依赖收集与触发更新流程解析 题目描述 : 请详细解析 Vue3 的响应式系统在源码层面如何实现依赖收集(track)和触发更新(trigger),包括核心数据结构的交互逻辑、执行时机以及异步调度细节。 1. 响应式系统的核心数据结构 Vue3 的响应式系统基于 Proxy 和 Reflect 实现,核心依赖三个数据结构: TargetMap :类型为 WeakMap<Target, KeyToDepMap> ,用于存储所有响应式对象与其依赖的映射。 KeyToDepMap :类型为 Map<Key, Dep> ,存储响应式对象的每个属性对应的依赖集合。 Dep :类型为 Set<ReactiveEffect> ,存储与属性相关的所有副作用函数(effect)。 2. 依赖收集(track)流程 当访问响应式对象的属性时,Proxy 的 get 拦截器会触发 track 函数: 步骤分解 : 检查当前活跃的 effect : 只有处于 activeEffect (当前正在执行的副作用函数)时才会收集依赖。 若 activeEffect 为 null (例如在非 effect 上下文中访问属性),则直接返回。 查找或创建依赖映射 : 从 targetMap 中查找当前对象( target )对应的 depsMap ,若不存在则创建新的 Map 。 从 depsMap 中查找当前属性( key )对应的 dep (依赖集合),若不存在则创建新的 Set 。 建立双向关联 : 将 activeEffect 添加到 dep 中(表示属性依赖此 effect)。 同时将 dep 添加到 activeEffect.deps 数组中(用于后续清理依赖)。 3. 触发更新(trigger)流程 当修改响应式对象的属性时,Proxy 的 set 拦截器会触发 trigger 函数: 步骤分解 : 查找依赖集合 : 从 targetMap 中获取 target 对应的 depsMap ,若不存在则直接返回(说明从未被依赖)。 从 depsMap 中获取 key 对应的 dep (依赖集合)。 优化重复 effect : 使用 new Set() 合并 dep 与其他可能相关的依赖(如数组的 length 变更),避免重复执行同一个 effect。 调度执行 effect : 遍历依赖集合,若 effect 配置了 scheduler (如组件更新器),则交给调度器处理(异步批量更新)。 否则直接同步执行 effect.run() 。 4. 异步调度与批量更新 Vue3 通过 scheduler 实现异步批量更新,避免重复渲染: 组件更新 effect 的 scheduler 会将更新任务推入队列,并通过 nextTick 批量执行。 示例 :多次修改响应式数据时,仅触发一次组件更新。 5. 依赖清理机制 Effect 重新执行前会清理旧依赖,避免无效依赖残留: 总结 : Vue3 的响应式系统通过 track 建立属性与 effect 的关联,通过 trigger 按需触发更新,结合调度器实现异步批量优化。源码层级的细节包括依赖集合的嵌套存储、双向关联的维护以及异步任务队列的管理,共同保证了响应式数据的高效更新。