forked from vuejs/core
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
05eb4e0
commit 30f89ff
Showing
6 changed files
with
1,065 additions
and
690 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 流程 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.