Appearance
如果说 effect
能知道收集到哪些依赖,下一次收集的时候尝试复用之前的链表节点,就不会出现重复收集依赖的问题了。
最直接的方法就是让它自己也有一个引用列表:
建立反向依赖链表:创建一个新的链表,让 effect
知道自己已经订阅了哪些 ref
,就能避免新增多余的链表节点。
实现复用:再次触发更新的时候,就可以根据已经订阅的依赖进行判断,如果已经收集过依赖,就不需要再创建新的链表节点。
关键点:
- 创建一个新的链表,让
effect
记录曾经收集过的依赖,这儿链表称之为deps
- 需要一个方法来判断
effect
是否正在重新执行依赖收集
初始化状态:在进入页面时,effect
收集依赖,将头节点和尾节点指向 link1
,link1
的 sub
指向 effect
。
建立反向依赖链表:
将现有的 Ref -> Link -> Effect
链表,反向建立 Effect -> Link -> Ref
链表,这样我们就可以通过关系链找到 effect
订阅过的所有依赖。
新的数据结构:
Effect:
deps
:通过 link 记录了 effect 依赖了哪些 refdepsTail
:链表尾节点
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
}
2. 修改 link
函数,建立双向链表关系
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
来设定三种状态
- 初始化(没收集过依赖):
deps
和depsTail
都是undefined
- 重新执行中(需要复用节点): 将
depsTail
临时设置为undefined
, 但保留deps
- 重新执行结束(收集完依赖):
depsTail
指向Link
当 effect
重新执行时,先将 depsTail
设置为 undefined
,表示正在重新收集依赖。这样在收集依赖时,如果发现 depsTail
是 undefined
,就知道这是在重新执行中,可以尝试复用已有的链表节点。
所以 我们的判断依据是: 只要 effect
存在头节点 deps
, 但是尾节点 depsTail
是 undefined
, 就表示正在重新执行中, 需要复用节点.
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
}
// ...新节点创建
}
完整流程
初次执行
- effect 初始化,
deps
和depsTail
都是undefined
- 收集依赖,创建
link1
,deps
和depsTail
都指向link1
flag
的subs
和subsTail
都指向link1
link1
的sub
指向effect
,dep
指向flag
link1
的nextSub
和prevSub
都是undefined
link1
的nextDep
和prevDep
都是undefined
- 完成初次依赖收集
第一次点击按钮
flag.value
由true
改为false
,触发setter
propagate
遍历flag
的订阅链表,找到link1
- 执行
link1.sub.run()
,即effect.run()
effect.run()
中将depsTail
设置为undefined
,表示正在重新收集依赖effect
重新执行,读取flag.value
,触发getter
getter
调用link(flag, effect)
收集依赖link
函数检查effect.deps
,发现deps
指向link1
,但depsTail
是undefined
- 检查
link1.dep
是否是当前的flag
,是的话复用link1
- 将
depsTail
指向link1
,表示依赖收集完成