Skip to content

如果说 effect 能知道收集到哪些依赖,下一次收集的时候尝试复用之前的链表节点,就不会出现重复收集依赖的问题了。

最直接的方法就是让它自己也有一个引用列表:

建立反向依赖链表:创建一个新的链表,让 effect 知道自己已经订阅了哪些 ref,就能避免新增多余的链表节点。

实现复用:再次触发更新的时候,就可以根据已经订阅的依赖进行判断,如果已经收集过依赖,就不需要再创建新的链表节点。

关键点

  1. 创建一个新的链表,让 effect 记录曾经收集过的依赖,这儿链表称之为 deps
  2. 需要一个方法来判断 effect 是否正在重新执行依赖收集

初始化状态:在进入页面时,effect 收集依赖,将头节点和尾节点指向 link1link1sub 指向 effect

建立反向依赖链表

将现有的 Ref -> Link -> Effect 链表,反向建立 Effect -> Link -> Ref 链表,这样我们就可以通过关系链找到 effect 订阅过的所有依赖。

新的数据结构

Effect

  • deps:通过 link 记录了 effect 依赖了哪些 ref
  • depsTail:链表尾节点

Ref(flag)

  • subs:通过 link 记录了哪些 effect 订阅了自己
  • subsTail:链表尾节点

Link(连接 effect 和 Ref,同时存在两个链表中):

  • sub:指向 effect(发起的订阅者)
  • dep:指向 ref(被订阅的对象)
  • nextDep / prevDep:指向 effect 链表中的下一个/上一个节点
  • nextSub / prevSub:指向 ref 链表中的下一个/上一个节点

接下来更新代码,实现新的数据结构:

1. 类型定义

ts
// effect.ts
export class ReactiveEffect {
// 依赖项链表的头节点
deps: Link | undefined
// 依赖项链表的尾节点
depsTail: Link | undefined
}
ts
// system.ts

/**
* 依赖项
*/
interface Dep {
subs: Link | undefined // 订阅者链表的头节点
subsTail: Link | undefined // 订阅者链表的尾节点
}

/**
* 订阅者
*/
interface Sub {
deps: Link | undefined
depsTail: Link | undefined
}

/**
* 链表节点
*/
export interface Link {
sub: Sub
nextSub: Link | undefined // 下一个节点
prevSub: Link | undefined // 上一个节点
dep: Dep
// 下一个依赖项节点
nextDep: Link | undefined
}
ts
export function link(dep: Dep, sub: ReactiveEffect): void {

const newLink: Link = {
 sub,
 dep, // 加入依赖项
 nextDep: undefined,
 nextSub: undefined,
 prevSub: undefined
}
// ... 

if (sub.depsTail) {
 sub.depsTail.nextDep = newLink
 sub.depsTail = newLink
} else {
 sub.deps = newLink
 sub.depsTail = newLink
}
}

3. 实现节点复用

每次 effect 重新执行的时候, 如何知道是 "第一次执行" 还是 "重新执行" 呢? 可以通过 effect 上的 deps depsTail 来设定三种状态

  • 初始化(没收集过依赖): depsdepsTail 都是 undefined
  • 重新执行中(需要复用节点): 将 depsTail 临时设置为 undefined, 但保留 deps
  • 重新执行结束(收集完依赖): depsTail 指向 Link

effect 重新执行时,先将 depsTail 设置为 undefined,表示正在重新收集依赖。这样在收集依赖时,如果发现 depsTailundefined,就知道这是在重新执行中,可以尝试复用已有的链表节点。

所以 我们的判断依据是: 只要 effect 存在头节点 deps, 但是尾节点 depsTailundefined, 就表示正在重新执行中, 需要复用节点.

ts
// effect.ts
run()
{
  const prevSub = activeSub

  activeSub = this
  this.depsTail = undefined // 重新执行中, 需要复用节点
  // ...
}
ts
// system.ts
export function link(dep, sub) {
  const currentDep = sub.depsTail
  const nextDep = currentDep === undefined ? sub.deps : undefined
  if (nextDep && nextDep.dep === dep) {
    sub.depsTail = nextDep
    return
  }
// ...新节点创建
}

完整流程

初次执行

  1. effect 初始化,depsdepsTail 都是 undefined
  2. 收集依赖,创建 link1depsdepsTail 都指向 link1
  3. flagsubssubsTail 都指向 link1
  4. link1sub 指向 effectdep 指向 flag
  5. link1nextSubprevSub 都是 undefined
  6. link1nextDepprevDep 都是 undefined
  7. 完成初次依赖收集

第一次点击按钮

  1. flag.valuetrue 改为 false,触发 setter
  2. propagate 遍历 flag 的订阅链表,找到 link1
  3. 执行 link1.sub.run(),即 effect.run()
  4. effect.run() 中将 depsTail 设置为 undefined,表示正在重新收集依赖
  5. effect 重新执行,读取 flag.value,触发 getter
  6. getter 调用 link(flag, effect) 收集依赖
  7. link 函数检查 effect.deps,发现 deps 指向 link1,但 depsTailundefined
  8. 检查 link1.dep 是否是当前的 flag,是的话复用 link1
  9. depsTail 指向 link1,表示依赖收集完成

最后更新时间: