前言
最新的一期Javascript Weekly
记载Vue 3.5
发布了,其中第一点改动提到了重构它reactity
的实现。而这个实现的灵感来自于preact
的signal
,使用了doubly linked-list
和version number
,而它的核心源码只有一个文件,算是比较易读的。
本文
数据结构
1 | type Node = { |
source
表示依赖项,target
表示订阅者,sources
通常存的是依赖链,是正序的,而targets
存的是订阅链,跟倒序的。targets
倒序可能是因为batched effect
是先进后出的,这样子从末尾开始notify
可以保证effect
按正向执行。
依赖追踪
初始时,首先会在Computed/Effect
执行callback
前设自己为全局的evalContext
,在callback
执行时如果get
signal
的value
,则会创建一个node
, 其source
设为这个signal
,target
设为这个Computed/Effect
,再把这个node
加入到Computed/Effect
的依赖链中以及signal
的订阅链中。
随后,当某些source
发生变化时,会执行这些订阅者的callback
。而此时,订阅者会执行prepareSources
这个步骤,目的是通过将依赖链的所有node
的version
置为-1
,标记上次的依赖项,当callback
执行完,在cleanupSources
这一步中,把version
依然是-1
的node
剔除出依赖链;prepareSources
除上述的作用以外,还会把node
设到source._node
里,_node
其实是跟当前evalContext
关联的,有种让source
切换到evalContext
相关的依赖链的上下文的意思。
通知去重
Computed/Effect
被通知后flags
会更新为NOTIFIED
,如果在同一个batch
里再被通知,就不会重复地加入过batchedEffect
的链表中。
批量更新
实现比较简单,batch
执行callback
、更新value
前增加batchDepth
,执行完callback
以后减少batchDepth
,当batchDepth
等于1
时才逐个执行effect
其他
Computed
的callback
是lazily evaluate
的,而effect
则是eager
的,这个跟vue
的computed
和watchEffect
是一样的。
后记
看preact
的blog
文,signal
的初版是用Set
来实现的,但Set
的问题是创建会比较expensive
,遍历的速度相对比较慢,而且由于依赖的顺序可能会改变,这时候用Set
来实现,可能得删除后重新加入,但这样的话文章里说会有新内存分配的可能性,这样会影响性能。而doubly linked-list
则很好地解决这些问题。