Skip to content

Commit

Permalink
chore: 阅读理解 ref 的初始化track与二维双向链变构建
Browse files Browse the repository at this point in the history
  • Loading branch information
baiwusanyu-c committed Feb 26, 2024
1 parent 05eb4e0 commit 30f89ff
Show file tree
Hide file tree
Showing 6 changed files with 1,065 additions and 690 deletions.
303 changes: 303 additions & 0 deletions aa.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,303 @@
### packages/reactivity/src/effect.ts
1.activeEffect 修改为 activeSub
2.DebuggerOptions、ReactiveEffectRunner不变,挪动了位置
3.ReactiveEffectOptions只包含 scheduler、allowRecurse、onStop,(TODO:其他删除了)
4.新增 EffectFlags (TODO: 作用??)
```typescript
export enum EffectFlags {
ACTIVE = 1 << 0,
RUNNING = 1 << 1,
TRACKING = 1 << 2,
NOTIFIED = 1 << 3,
DIRTY = 1 << 4,
ALLOW_RECURSE = 1 << 5,
NO_BATCH = 1 << 6,
}
```
5.新增 Subscriber 类型,用于定位和追踪依赖列表
```typescript
export interface Subscriber extends DebuggerOptions {
/**
* Head of the doubly linked list representing the deps
* 依赖双链表的头部
* @internal
*/
deps?: Link
/**
* Tail of the same list
* 同一列表尾部
* @internal
*/
depsTail?: Link
/**
* @internal
* TODO:作用
*/
flags: EffectFlags
/**
* @internal
* TODO:作用
*/
notify(): void
}
```
6.新增链表节点
```typescript
export interface Link {
// 节点对应的依赖对象
dep: Dep
// 节点对应的订阅对象
sub: Subscriber

/**
* - Before each effect run, all previous dep links' version are reset to -1
* - During the run, a link's version is synced with the source dep on access
* - After the run, links with version -1 (that were never used) are cleaned
* up
* 在 effect 运行前,所有'之前的'依赖链版本会重置为 - 1,
* 在运行期间,一个链的版本会随着dep依赖源的方法2⃣同步
* 在运行之后,所有版本为 - 1 的依赖会被清理,即没有被访问
*/
version: number

/**
* Pointers for doubly-linked lists
* 这是一个二维的链结构,每个link 节点都指向
* deps、sub、
*/
nextDep?: Link
prevDep?: Link

nextSub?: Link
prevSub?: Link

prevActiveLink?: Link
}
```
7. ReactiveEffect 对象变化
7.1)deps 由数组边为链一个link节点、scheduler从构造器中变为对象属性
7.2)新增depsTail、flags、nextEffect




根据海老师提供的图,我们可以判断 sub,订阅者,实际上就是一个个 ReactiveEffect 对象,它代表者订阅依赖,并触发依赖的追踪和接受依赖触发执行(即执行fn)




## ref 初始化收集依赖流程
const a = ref(1), ref 内部会创建一个 ref 实例对象 RefImpl, RefImpl 在构造函数中会初始化一个 dep 对象,
并在访问 a 时,将访问的函数作为依赖,调用 this.dep.track() 收集,
```typescript
it('should be reactive', () => {
const a = ref(1)
let dummy
const fn = vi.fn(() => {
dummy = a.value
})
effect(fn)
expect(fn).toHaveBeenCalledTimes(1)
expect(dummy).toBe(1)
a.value = 2
expect(fn).toHaveBeenCalledTimes(2)
expect(dummy).toBe(2)
// same value should not trigger
a.value = 2
expect(fn).toHaveBeenCalledTimes(2)
})

```
当设置 a 时,即触发 RefImpl 上的 set 方法,其内部会 触发 dep 的 this.dep.trigger() 方法进行触发
最终效果时自动执行了 fn。
那么我们来看一下 dep 是什么东西。
当ref构造函数执行时,实际上 dep已经初始化了,这个 dep 对象包含这些内容
````
computed = undefined
version = 0
activeLink = undefined
subs = undefined
track: () => {}
trigger: () => {}
notify: () => {}
````
但这只是初始化,其真正的数据结构(二维双向链表)还没有建立,但是当 effect 执行 fn 触发 ref 的 get 方法时,
在执行完this.dep.track()方法后, this.dep 却发生了变化,那我们看看从 effect 执行,到 this.dep.track() 执行之间,
究竟做了什么。
执行effect,这个函数其实没有太多变化,重点在于 ReactiveEffect 对象的变化,它新增了这几个属性
```typescript
class ReactiveEffect {
/**
* @internal
*/
deps?: Link = undefined
/**
* @internal
*/
depsTail?: Link = undefined
/**
* @internal
*/
flags: EffectFlags = EffectFlags.ACTIVE | EffectFlags.TRACKING
/**
* @internal
*/
nextEffect?: ReactiveEffect = undefined
}
```
从 effect 的逻辑中看出 在初始化 ReactiveEffect 对象时,构造函数并没有什么太多动作
此时我们的到 effect 的属性为, 其中 flags 为 5 表示这个 ReactiveEffect 是活动的或正在追踪
```typescript
export enum EffectFlags {
ACTIVE = 1 << 0,
RUNNING = 1 << 1,
TRACKING = 1 << 2,
NOTIFIED = 1 << 3,
DIRTY = 1 << 4,
ALLOW_RECURSE = 1 << 5,
NO_BATCH = 1 << 6,
}

deps = undefined
depsTail = undefined
flags = 5
nextEffect = undefined
scheduler = undefined
```
继续往下, 我们执行 e.run, 那么看看 run 方法由哪些变化
```typescript
const run = () => {

// TODO:作用待定,可能被错误清理,这要再次运行一下 fn
if (!(this.flags & EffectFlags.ACTIVE)) {
// stopped during cleanup
return this.fn()
}

// TODO:作用待定,可能是 处理嵌套场景,先处理深层次的 effect
// 标记 当前 effect 对象正在执行 fn 函数
this.flags |= EffectFlags.RUNNING
// TODO:作用待定
// 准备 dep,初始化时,this.deps 是 undefined,
// 初始化时 prepareDeps 无用
prepareDeps(this)
// TODO:作用待定
// 缓存上一个 activeSub 和 shouldTrack
const prevEffect = activeSub
const prevShouldTrack = shouldTrack
// 设置 activeSub 和 shouldTrack 为 当前 effect 对象 和 true
activeSub = this
shouldTrack = true

try {
return this.fn()
} finally {
if (__DEV__ && activeSub !== this) {
warn(
'Active effect was not restored correctly - ' +
'this is likely a Vue internal bug.',
)
}
// TODO:作用待定, 清除 dep
cleanupDeps(this)
activeSub = prevEffect
shouldTrack = prevShouldTrack
this.flags &= ~EffectFlags.RUNNING
}
}
```
初始化时,执行fn,然后访问 ref 对象,触发get方法进行依赖追踪,那么我们现在可以回过头来看看 this.dep.track()
```typescript
const track = (debugInfo) => {
if (!activeSub || !shouldTrack) {
return
}

// 初始化时 this.activeLink 是 undefined
let link = this.activeLink
// 初始化 activeLink
// activeSub 在 effect 的 run 方法中设置了的
// 至此初始化,dep 对象 的 activeLink 属性包含了自身,以及对应的 effect 对象
// TODO(具体怎么触发 sub)
if (link === undefined || link.sub !== activeSub) {
link = this.activeLink = {
dep: this,
sub: activeSub,
version: this.version,
nextDep: undefined,
prevDep: undefined,
nextSub: undefined,
prevSub: undefined,
prevActiveLink: undefined,
}

// add the link to the activeEffect as a dep (as tail)
// 初始化时 activeSub.deps = activeSub.depsTail
// activeSub.deps = link
// 此时建立了 effect 对象 与 dep 的联系,即存储在
// deps 中。
// 对于 dep 对象,可以通过 Link 访问自己,可以通过 sub 访问 effect 对象
// 对于 dep 对象,可以通过 deps 访问到 Link,进而访问到 dep 对象 和自己
if (!activeSub.deps) {
activeSub.deps = activeSub.depsTail = link
} else {
link.prevDep = activeSub.depsTail
activeSub.depsTail!.nextDep = link
activeSub.depsTail = link
}

// 初始化 7 & 4 =》 4
// 初始化 将 Link 对象存储都 dep 对象的 subs 属性中
if (activeSub.flags & EffectFlags.TRACKING) {
addSub(link)
}
} else if (link.version === -1) {
// reused from last run - already a sub, just sync version
link.version = this.version

// If this dep has a next, it means it's not at the tail - move it to the
// tail. This ensures the effect's dep list is in the order they are
// accessed during evaluation.
if (link.nextDep) {
const next = link.nextDep
next.prevDep = link.prevDep
if (link.prevDep) {
link.prevDep.nextDep = next
}

link.prevDep = activeSub.depsTail
link.nextDep = undefined
activeSub.depsTail!.nextDep = link
activeSub.depsTail = link

// this was the head - point to the new head
if (activeSub.deps === link) {
activeSub.deps = next
}
}
}

// debugger hook api
if (__DEV__ && activeSub.onTrack) {
activeSub.onTrack(
extend(
{
effect: activeSub,
},
debugInfo,
),
)
}

return link
}
```
可以看到 track 追踪依赖的初始化主要逻辑是创建一个 Link 对象,它存储了 effect 对象和 dep 对象,使得二者之间可以相互访问,
然后使用 addSub 方法,将 Link 对象存储都 dep 对象的 subs 属性中。
根据海老师的图示,可知所谓的二维双向链表之间的数据结构是指
![img.png](img.png)
一维:即 dep 方向,链表从 dep 对象开始,节点存储在 subs 属性中,它存储的是 Link 对象
二维:即 sub 方向,其 sub 其本质是 ReactiveEffect 对象,链表从一个 ReactiveEffect 对象开始,节点存储存储在 deps 属性上
通过 Link 对象之间 nextDep 、prevDep 、nextSub 、prevSub 指针相互链接,最终形成一个矩阵,即所谓二维双向链表

## ref trigger 流程
Binary file added img.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
18 changes: 17 additions & 1 deletion packages/reactivity/src/dep.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
* Incremented every time a reactive change happens
* This is used to give computed a fast path to avoid re-compute when nothing
* has changed.
* bwsy: 每次发生反应性更改时递增 这用于为计算提供快速路径,以避免在没有任何更改时重新计算。
*/
export let globalVersion = 0

Expand All @@ -25,21 +26,26 @@ export class Dep {
version = 0
/**
* Link between this dep and the current active effect
* bwsy:此 dep 对象与当前活动的 effect 之间的链接,
*/
activeLink?: Link = undefined
/**
* Doubly linked list representing the subscribing effects (tail)
* bwsy:订阅者双链
*/
subs?: Link = undefined

constructor(public computed?: ComputedRefImpl) {}

track(debugInfo?: DebuggerEventExtraInfo): Link | undefined {
debugger

Check failure on line 41 in packages/reactivity/src/dep.ts

View workflow job for this annotation

GitHub Actions / lint-and-test-dts

Unexpected 'debugger' statement
if (!activeSub || !shouldTrack) {
return
}

// bwsy:初始化时 undefined
let link = this.activeLink
// bwsy:初始化 activeLink
// bwsy:activeSub 在 effect 的 run 方法中设置了的
if (link === undefined || link.sub !== activeSub) {
link = this.activeLink = {
dep: this,
Expand All @@ -53,6 +59,12 @@ export class Dep {
}

// add the link to the activeEffect as a dep (as tail)
// bwsy:初始化时 activeSub.deps = activeSub.depsTail
// activeSub.deps = link
// 此时建立了 effect 对象 与 dep 的联系,即存储在
// deps 中。
// 对于 dep 对象,可以通过 Link 访问自己,可以通过 sub 访问 effect 对象
// 对于 dep 对象,可以通过 deps 访问到 Link,进而访问到 dep 对象 和自己
if (!activeSub.deps) {
activeSub.deps = activeSub.depsTail = link
} else {
Expand All @@ -61,6 +73,7 @@ export class Dep {
activeSub.depsTail = link
}

//bwsy: 初始化 7 & 4 =》 4, 初始化 将 Link 对象存储都 dep 对象的 subs 属性中
if (activeSub.flags & EffectFlags.TRACKING) {
addSub(link)
}
Expand All @@ -71,6 +84,8 @@ export class Dep {
// If this dep has a next, it means it's not at the tail - move it to the
// tail. This ensures the effect's dep list is in the order they are
// accessed during evaluation.
// bwsy:如果这个 dep 有下一个,则意味着它不在尾部 - 将其移动到尾部。
// 这可确保 effect 的 dep 列表按照它们在评估期间访问的顺序排列。
if (link.nextDep) {
const next = link.nextDep
next.prevDep = link.prevDep
Expand All @@ -90,6 +105,7 @@ export class Dep {
}
}

// bwsy:debugger hook api
if (__DEV__ && activeSub.onTrack) {
activeSub.onTrack(
extend(
Expand Down
Loading

0 comments on commit 30f89ff

Please sign in to comment.