smartskip智能跳绳软件
15.27MB · 2025-12-21
昨天我们了解到,当 effect 函数依赖多个响应式变量时,会再次触发指数级更新。
我们来回顾一下之前的做法:
run() 函数首先会将 depsTail 设为 undefined。
// effect.ts
run(){
const prevSub = activeSub
activeSub = this
// 开始执行,将尾节点设为 undefined
this.depsTail = undefined
// ...
}
后续的依赖收集中,depsTail 会被赋值并指向已复用的节点。
export function link(dep, sub){
if(sub.depsTail === undefined && sub.deps){
if(sub.deps.dep === dep){
sub.depsTail = sub.deps // 移动尾节点指针,指向刚刚复用的节点
return
}
}
// ...
}
完整的判断逻辑:
export function link(dep, sub){
const currentDep = sub.depsTail;
// 仅在 depsTail 为 undefined 时,才尝试从头节点 sub.deps 开始复用
if (currentDep === undefined && sub.deps) {
// 只会检查依赖链表的第一个节点
if (sub.deps.dep === dep) {
sub.depsTail = sub.deps; // 成功后移动指针
return;
}
}
// 若不符合上述条件,则直接创建新节点...
}
深入分析后,我们了解到问题出在依赖节点的复用逻辑上:
sub.deps)。flag)复用成功,depsTail 就被赋值。这导致后续依赖(例如 count)在检查时,因为 currentDep === undefined 条件不成立,直接跳过了复用检查,从而盲目地创建了新的 Link 节点。在旧的逻辑中,depsTail 只是一个标记,用于判断是否是“第一次执行复用”。现在我们要把它升级为一个“进度指针”。它的作用是标记当前复用检查进行到了链表的哪个位置。
这个思路提供了一个关键点:“当 depsTail 存在时,代表依赖链表的遍历与复用正在进行中。 ”
因此,我们就可以在原有的 link 函数中,增加一个额外的检查逻辑。
当第一个 if 条件不成立,但 depsTail (currentDep) 确实存在时,就意味着我们不应该从头节点开始检查,而应该从当前 depsTail 所在节点的下一个节点 (currentDep.nextDep) 继续检查。
依照上面的执行逻辑,flag 复用成功后,depsTail 指向 Link1。我们需要新增的逻辑,就是要从 Link1 的 nextDep,也就是 Link2,继续进行检查。
检查逻辑的核心:如果尾节点 (depsTail) 存在,并且这个尾节点还有下一个节点 (nextDep),我们就应该检查这个 nextDep 是否是我们要找的目标,如果是,就直接复用它。
depsTail 是否有值。如果有,代表复用已经开始,并且当前进度停在链表的某个节点上(像是 Link1)。depsTail 所指向节点的“下一个节点”,也就是 currentDep.nextDep (对应到我们的例子,就是 Link2)。currentDep.nextDep.dep),是否就是我们当前正要处理的依赖 (count)。depsTail 这个“进度指针”向前移动到这个 nextDep 节点上。const currentDep = sub.depsTail
if (currentDep === undefined && sub.deps) {
// 依赖链表头节点的 ref 与当前要连接的 ref 相等的话,表示之前收集过依赖
if (sub.deps.dep === dep) {
sub.depsTail = sub.deps // 移动尾节点指针,指向刚刚复用的节点
return // 直接返回,不新增节点
}
} else if (currentDep) { // 尾节点存在
// 尾节点的 nextDep 所连接的 ref,等于当前要连接的 ref
if (currentDep.nextDep?.dep === dep) {
sub.depsTail = currentDep.nextDep
// 移动尾节点指针,复用尾节点的 nextDep
return
}
}
目前这个结构虽然已经能正确运行,但我们可以将它重构得更简洁。我们发现无论是从头开始还是从中途继续,我们的目标都是找到“下一个待检查节点” 。
const currentDep = sub.depsTail
// 核心逻辑:根据 currentDep 是否存在,来决定下一个要检查的节点
const nextDep = currentDep === undefined ? sub.deps : currentDep.nextDep
// 如果 nextDep 存在,且 nextDep.dep 等于我当前要收集的 dep
if (nextDep && nextDep.dep === dep) {
sub.depsTail = nextDep // 移动指针
return
}
获取 depsTail 当前值 (currentDep = sub.depsTail)。
根据 depsTail 决定要检查哪个节点:
depsTail 为 undefined → 从头节点开始 (nextDep = sub.deps)。depsTail 有值 → 检查下一个 (nextDep = currentDep.nextDep)。检查是否可以复用 (nextDep 必须存在且其 .dep 属性与当前依赖匹配)。
如果可以复用:
depsTail 到 nextDep (记录遍历进度)。Link,提前返回。通过将 depsTail 指针从一个单纯的“尾部标记”升级为“遍历进度指针”,我们解决了多变量依赖下的节点复用问题。
重构后的代码不仅修复了指数级更新的 Bug,更用统一的逻辑处理了不同情况下的节点检查。
想了解更多 Vue 的相关知识,抖音、B站搜索我师父「远方os」,一起跟日安当同学。
15.27MB · 2025-12-21
40.05MB · 2025-12-21
50.79MB · 2025-12-21