Vue3 的响应式系统源码级 effect 的 active 状态与依赖清理机制
字数 1427 2025-12-12 01:27:01

Vue3 的响应式系统源码级 effect 的 active 状态与依赖清理机制

描述
在 Vue3 的响应式系统中,effect 是响应式副作用的核心抽象,用于追踪依赖并在数据变化时重新执行。其中 active 状态用于控制 effect 是否参与依赖收集,而依赖清理机制确保在 effect 重新执行前正确清理旧依赖,避免无效更新和内存泄漏。理解这两个机制对于掌握 effect 的生命周期和性能优化至关重要。

循序渐进讲解

  1. effect 的基本结构
    Vue3 中每个 effect 是一个封装了用户副作用函数的对象,包含以下关键属性:

    const effect = {
      fn,          // 用户传入的副作用函数
      scheduler,   // 调度器(可选)
      active: true, // 是否激活
      deps: []     // 依赖列表(存储了所有包含此 effect 的依赖集合)
    }
    
    • active 初始为 true,表示 effect 处于激活状态,会正常追踪依赖。
    • deps 是一个数组,存储了所有“依赖集合”(即包含此 effect 的 Set<effect>)。例如,当响应式对象属性 obj.a 被访问时,会将当前 effect 添加到 obj.a 对应的依赖集合中,同时将该集合记录到 effect.deps 中。
  2. active 状态的作用

    • 激活状态(active: true):effect 会被 track 收集到依赖关系中,且在 trigger 时会被执行或调度。
    • 非激活状态(active: false):effect 不会被 track 收集,也不会被 trigger 触发。这通常发生在:
      • effect 被手动停止(stop 被调用)。
      • 组件卸载时,组件相关的 effect 会被停止。
    • 示例:调用 runner.effect.stop() 会将 active 设为 false,并清空依赖关系。
  3. 依赖清理(cleanup)的必要性
    考虑以下场景:

    const obj = reactive({ a: 1, b: 2 })
    effect(() => {
      console.log(obj.a ? obj.b : 0)
    })
    // 首次执行:访问 obj.a 和 obj.b,收集依赖
    obj.a = 0 // 触发 effect 重新执行
    // 第二次执行:因 obj.a 为 0,只访问 obj.a,不再访问 obj.b
    
    • 第一次执行后,effect 被收集到 obj.aobj.b 的依赖集合中。
    • obj.a 变为 0 后,第二次执行时不再访问 obj.b。如果不清理,那么 obj.b 的依赖集合中仍保留此 effect,导致后续 obj.b 变化时仍会触发 effect(无效更新)。
  4. cleanup 实现机制
    在 effect 执行前,Vue3 会执行清理操作:

    function cleanup(effect) {
      const { deps } = effect
      if (deps.length) {
        for (let i = 0; i < deps.length; i++) {
          deps[i].delete(effect) // 从每个依赖集合中移除当前 effect
        }
        deps.length = 0 // 清空 deps 数组
      }
    }
    
    • 遍历 effect.deps,从每个依赖集合(Set<effect>)中删除当前 effect。
    • 清空 effect.deps 数组,为新一轮依赖收集做准备。
  5. cleanup 的执行时机
    effect.run()effect.scheduler() 中,执行用户副作用函数前会先调用 cleanup

    function run() {
      if (!this.active) return
      cleanup(this) // 执行清理
      // 设置当前激活的 effect,执行用户函数,收集新依赖...
    }
    
    • 清理 → 执行用户函数 → 重新收集依赖,形成一个闭环。
  6. active 与 cleanup 的协同

    • 当 effect 被停止(active = false)时,也会调用 cleanup 清空所有依赖,确保无残留引用。
    • 如果 effect 已非激活状态,即使被 trigger 也不会执行,因为 run 函数会提前返回。
  7. 实际应用示例

    const runner = effect(() => { ... })
    // 一段时间后停止 effect
    runner.effect.stop()
    // 此时 active = false,依赖被清理,后续数据变化不再触发此 effect
    

总结
active 状态控制了 effect 的“生死”,决定其是否参与响应式系统。cleanup 机制确保了每次重新执行前清理旧依赖,避免无效更新和内存泄漏。两者结合保证了 effect 在动态依赖场景下的正确性和性能,是 Vue3 响应式系统健壮性的基石。

Vue3 的响应式系统源码级 effect 的 active 状态与依赖清理机制 描述 : 在 Vue3 的响应式系统中, effect 是响应式副作用的核心抽象,用于追踪依赖并在数据变化时重新执行。其中 active 状态用于控制 effect 是否参与依赖收集,而 依赖清理机制 确保在 effect 重新执行前正确清理旧依赖,避免无效更新和内存泄漏。理解这两个机制对于掌握 effect 的生命周期和性能优化至关重要。 循序渐进讲解 : effect 的基本结构 Vue3 中每个 effect 是一个封装了用户副作用函数的对象,包含以下关键属性: active 初始为 true ,表示 effect 处于激活状态,会正常追踪依赖。 deps 是一个数组,存储了所有“依赖集合”(即包含此 effect 的 Set<effect> )。例如,当响应式对象属性 obj.a 被访问时,会将当前 effect 添加到 obj.a 对应的依赖集合中,同时将该集合记录到 effect.deps 中。 active 状态的作用 激活状态(active: true) :effect 会被 track 收集到依赖关系中,且在 trigger 时会被执行或调度。 非激活状态(active: false) :effect 不会被 track 收集,也不会被 trigger 触发。这通常发生在: effect 被手动停止( stop 被调用)。 组件卸载时,组件相关的 effect 会被停止。 示例:调用 runner.effect.stop() 会将 active 设为 false ,并清空依赖关系。 依赖清理(cleanup)的必要性 考虑以下场景: 第一次执行后,effect 被收集到 obj.a 和 obj.b 的依赖集合中。 当 obj.a 变为 0 后,第二次执行时不再访问 obj.b 。如果不清理,那么 obj.b 的依赖集合中仍保留此 effect,导致后续 obj.b 变化时仍会触发 effect(无效更新)。 cleanup 实现机制 在 effect 执行前,Vue3 会执行清理操作: 遍历 effect.deps ,从每个依赖集合( Set<effect> )中删除当前 effect。 清空 effect.deps 数组,为新一轮依赖收集做准备。 cleanup 的执行时机 在 effect.run() 或 effect.scheduler() 中,执行用户副作用函数前会先调用 cleanup : 清理 → 执行用户函数 → 重新收集依赖,形成一个闭环。 active 与 cleanup 的协同 当 effect 被停止( active = false )时,也会调用 cleanup 清空所有依赖,确保无残留引用。 如果 effect 已非激活状态,即使被 trigger 也不会执行,因为 run 函数会提前返回。 实际应用示例 总结 : active 状态控制了 effect 的“生死”,决定其是否参与响应式系统。 cleanup 机制确保了每次重新执行前清理旧依赖,避免无效更新和内存泄漏。两者结合保证了 effect 在动态依赖场景下的正确性和性能,是 Vue3 响应式系统健壮性的基石。