2025.09.09

倍增触发:多依赖场景下的通知风暴解析

❓ 问题引入:几何级增长的触发次数

解决了单个响应式变量的重复收集问题后,我们将目光转向更复杂的 多依赖 场景。当一个 effect 同时依赖多个 ref 时,触发次数再次失控。


<button id="btn">增加计数</button>
<script type="module">
  // ... 导入 ref, effect
  const flag = ref(true)
  const count = ref(0)

  const e = effect(() => {
    flag.value  // 依赖 1
    count.value // 依赖 2
    console.count('effect 触发次数')
  })

  btn.onclick = () => {
    count.value++ // 仅修改 count
  }
</script>
    现象: 每次点击按钮,effect 的触发次数呈几何级增长。

  1. 初始化阶段: 建立有序双向链表 首次执行 effect 时,依赖收集是清晰有序的:
    • 读取顺序: flag.value -> count.value
    • link 顺序: flag 依赖 link1, count 依赖 link2
    • Effect 内部: deps 链表 link1 -> link2
  2. 第一次点击: 更新 count.valuecount.value++ 触发更新,effect 重新执行。
    2.1 🔴 重新读取 flag.value
    • 状态标记: effect.run()depsTail 重置为 undefined, 进入复用模式
    • 当前 link: effect.deps 执行 link1
    • 复用检查: 检查 link1.dep 是否为当前依赖 flag
    • 结果: link1.dep(flag) === dep(flag) ✅ 复用成功
    • 指针移动: depsTail 移动到 link1

    2.2 🔵 重新读取 count.value
    • 当前 Link: 由于复用成功, depsTail 现在指向 link1
    • 复用检查: 再次检查 (sub.deps && !sub.depsTail) 条件 -> 不成立 (depsTail 不为 undefined)
    • 系统判断: 判断为 "正在尾部追加依赖" 状态
    • 结果: ❌ 冗余创建,系统忽略了 link2 的存在,创建了新的 link3 节点,并将其追加到 link1 之后。]
  3. 问题后果: 链表分叉与几何级增长 由于 count 的依赖没有复用 link2,反而创建了冗余的 link3,导致:
    • 依赖链表结构错误: effectdeps 链变化成了 link1 -> link3, 且 link2 被遗弃但并未清理
    • 下一次触发: 随着 link3 的存在, 在下次更新中再次触发 effect 执行, 产生单依赖场景相似的依赖重复收集 的恶性循环

💡 核心问题

复用机制的判断逻辑过于简单,它只适用于 依赖顺序与上次收集顺序完全一致数量不变 的场景。一旦读取顺序或依赖数量发生变化,depsTail 的状态就会被错误设置,导致节点冗余创建。