2025.09.10

迭代复用:高效解决多依赖重复收集

前文我们发现,在多依赖场景下,即使引入了节点复用机制,由于 依赖读取顺序的变化 或 状态管理不当,依然会导致冗余 Link 节点的创建。问题的核心在于:复用检查缺乏迭代能力。

💣 多依赖场景下的陷阱:Effect 为何被成倍触发?

前文我们发现,在多依赖场景下,即使引入了节点复用机制,由于 依赖读取顺序的变化状态管理不当,依然会导致冗余 Link 节点的创建。问题的核心在于:复用检查缺乏迭代能力

原始实现的局限性

我们原始的复用逻辑,通过将 depsTail 临时置为 undefined 来标记“重新执行中”状态,但它存在以下致命缺陷:

缺陷描述后果
局限检查复用逻辑仅检查并对比依赖链表的 第一个节点 (sub.deps)。无法处理第二个及后续依赖的复用。
状态变更过早一旦第一个节点复用成功,depsTail 会立即指向该节点。后续的依赖项无法进入复用检查状态(因为 depsTail 不再是 undefined)。

💡 优化思路

要解决这个问题,我们需要引入一个 迭代机制,在复用阶段让检查指针(由 depsTail 充当)能够沿着上次收集的链表逐步前进,而不是每次都回到起点或被错误地锁定。

注:之前的图解中有误,感谢指正 😊(effect 的尾节点应为:subsTail → depsTail)

depsTail 状态含义下一个待检查节点(nextDep)
undefined链表尚未开始复用(或链表为空)。sub.deps (头节点)
存在当前已经复用到此节点sub.depsTail.nextDep (下一个节点)

🛠️ 代码优化

// system.ts -> link 函数
export function link(dep: Dep, sub: ReactiveEffect): void {
  const currentDep = sub.depsTail

  // 1. 确定下一个待检查的节点 (迭代前进的关键)
  const nextDep = (currentDep === undefined)
    ? sub.deps // 情况一:从链表头部开始检查
    : currentDep.nextDep // 情况二:从已复用节点的下一个节点开始检查

  // 2. 执行复用检查
  if (nextDep && nextDep.dep === dep) {
    // 3. 复用成功:移动 depsTail 指针,指向当前复用的节点
    sub.depsTail = nextDep
  }

  // 4. 如果检查失败,则执行创建新节点的逻辑
  // ... (创建新 Link 节点,并将其追加到当前的 depsTail 之后)
}

🚀 完整流程

阶段动作currentDepnextDep结果
重置标记effect.run() 执行depsTail -> undefined
读取 flaglink(flag -> effect)undefinedsub.deps(link1)link1.dep === flag => 复用成功
depsTail -> link1
读取 countlink(count -> effect)link1link1.nextDep(link2)link2.dep === count => 复用成功
depsTail -> link2
收集完成链表结构保持不变, 没有冗余节点