[!IMPORTANT]
这是 alien-signals 1.0版本,vue3.6已经升级到2.0版本了
众所周知,Vue 3.6 计划引入一个全新的响应式机制:
alien-signals,用来进一步优化 Vue 的响应式系统。目前 vue3.6 还没正式发布,但可以先通过以下命令打包
alien-signals源码:esbuild src/index.ts --bundle --format=esm --outfile=esm/index.mjs
打包后的代码还不到 500 行,体积小、结构也比较清晰。趁现在还不那么复杂,我正在尝试解析一下
alien-signals的源码,顺便记录一些理解过程
首先我们先有一个 2x2 的单元测试,其中 fn1 和 fn2 分别有两个依赖 count1、count2
test("debugger 2*2", () => {
const count1 = signal(1);
const count2 = signal(100);
effect(function fn1() {
console.log(`effect1-> count1 is: ${count1()}`);
console.log(`effect1-> count2 is: ${count2()}`);
});
effect(function fn2() {
console.log(`effect2-> count1 is: ${count1()}`);
console.log(`effect2-> count2 is: ${count2()}`);
});
count1(2);
count2(200);
});
依赖收集
以下是 signal 的源码(build 后),我们重点关注 this,也就是 dep,后续我们用 蓝色 表示 dep。
function signal(initialValue) {
return signalGetterSetter.bind({
currentValue: initialValue,
subs: void 0,
subsTail: void 0,
});
}
接下来是 effect 的源码(build 后),这里我们重点关注 e,也就是 sub,后续我们用 黄色 表示 sub。
function effect(fn) {
// sub
const e = {
fn,
subs: void 0,
subsTail: void 0,
deps: void 0,
depsTail: void 0,
flags: 2 /* Effect */,
};
// 省略部分与当前单元测试无关的代码
const prevSub = activeSub;
activeSub = e;
try {
e.fn();
} finally {
activeSub = prevSub;
}
// 省略部分与当前单元测试无关的代码
}
在 effect 中,fn 会被默认执行一次以进行初始的依赖收集。当执行 fn1 时,我们可以得到以下数据:
在 fn1 中访问 count1() 时,会触发 link(this, activeSub),将当前的依赖和订阅关联起来。
function signalGetterSetter<T>(this: Signal<T>, ...value: [T]): T | void {
if (activeSub !== undefined) {
+ 注意这里的 link
link(this, activeSub);
}
return this.currentValue;
}
link 函数会尝试复用节点。如果无法复用,说明这是一个新的 link,因此会执行 linkNewDep(dep1, sub1, undefined, undefined)。
function link(dep: Dependency, sub: Subscriber): Link | undefined {
// 获取当前 sub 的最后一个依赖
const currentDep = sub.depsTail;
// ...
// 获取 currentDep 的下一个依赖。如果 depsTail 不存在,就是当前 sub 的第一个依赖。
// 这段逻辑主要与依赖触发后的重新依赖收集有关,暂时不会执行这个 if 里面的逻辑,主要用于复用节点。
const nextDep = currentDep !== undefined ? currentDep.nextDep : sub.deps;
if (nextDep !== undefined && nextDep.dep === dep) {
sub.depsTail = nextDep;
return;
}
// ...
return linkNewDep(dep, sub, nextDep, currentDep);
}
linkNewDep 会创建一个 newLink 节点,用于关联 dep 和 sub。
function linkNewDep(
dep: Dependency,
sub: Subscriber,
nextDep: Link | undefined,
depsTail: Link | undefined
): Link {
const newLink: Link = {
dep,
sub,
nextDep,
prevSub: undefined,
nextSub: undefined,
};
// 如果 depsTail 不存在,表示 currentDep 不存在,这是一个新的 sub。
if (depsTail === undefined) {
sub.deps = newLink;
} else {
depsTail.nextDep = newLink;
}
// 如果当前 dep 没有订阅,那么 dep1 的 subs 指向第一个订阅 sub1。
if (dep.subs === undefined) {
dep.subs = newLink;
} else {
const oldTail = dep.subsTail!;
newLink.prevSub = oldTail;
oldTail.nextSub = newLink;
}
// 更新尾部指针
sub.depsTail = newLink;
dep.subsTail = newLink;
return newLink;
}
第一次 linkNewDep 后的依赖收集结果如下:
接下来开始收集 count2 的依赖,同样会调用 link 和 linkNewDep 函数。根据上一次的依赖关系图,可以推导出:
linkNewDep(dep2, sub1, undefined, dep1.depsTail)
function linkNewDep(
dep: Dependency,
sub: Subscriber,
nextDep: Link | undefined,
depsTail: Link | undefined
) {
// 根据上述可知,depsTail -> dep1 -> depsTail 的 newLink
if (depsTail === undefined) {
// 不会执行
sub.deps = newLink;
} else {
// 这次执行这个
depsTail.nextDep = newLink;
}
// 当前的 dep2 没有被订阅,那么 dep2 的 subs 指向第一个订阅 sub1。
if (dep.subs === undefined) {
dep.subs = newLink;
} else {
// 不会执行
const oldTail = dep.subsTail!;
newLink.prevSub = oldTail;
oldTail.nextSub = newLink;
}
// 更新尾部指针
sub.depsTail = newLink;
dep.subsTail = newLink;
}
第二次 linkNewDep 后的依赖收集结果如下:
至此,第一个 effect 的依赖收集完成。接下来开始第二个 effect 的依赖收集。根据 effect 的源码,我们知道会创建一个新的订阅 sub2,此时的依赖关系图如下:
effect(function fn2() {
console.log(`effect2-> count1 is: ${count1()}`);
console.log(`effect2-> count2 is: ${count2()}`);
});
执行 fn2,正式开始依赖收集:
- 访问
count1()时,同样会依次执行link和linkNewDep函数。根据上一次的依赖关系图,可以推导出:
linkNewDep(dep1, sub2, undefined, undefined)
function linkNewDep(
dep: Dependency,
sub: Subscriber,
nextDep: Link | undefined,
depsTail: Link | undefined
) {
// 根据上述可知,depsTail -> undefined
if (depsTail === undefined) {
// 这次执行这个
sub.deps = newLink;
} else {
// 不会执行这个
depsTail.nextDep = newLink;
}
// 当前的 dep1 已经被订阅,subs 指向 newLink-sub->sub1。
if (dep.subs === undefined) {
dep.subs = newLink;
} else {
// 执行这个
const oldTail = dep.subsTail!;
newLink.prevSub = oldTail;
oldTail.nextSub = newLink;
}
// 更新尾部指针
sub.depsTail = newLink;
dep.subsTail = newLink;
}
此时的依赖关系图更新为:
- 访问
count2()时,同样会依次执行link和linkNewDep函数。根据上一次的依赖关系图,可以推导出:
linkNewDep(dep2, sub2, undefined, dep1.depsTail)
function linkNewDep(
dep: Dependency,
sub: Subscriber,
nextDep: Link | undefined,
depsTail: Link | undefined
) {
// 根据上述可知,depsTail -> dep1 -> depsTail 的 newLink
if (depsTail === undefined) {
// 不会执行
sub.deps = newLink;
} else {
// 这次执行这个
depsTail.nextDep = newLink;
}
// 当前的 dep2 已经被 sub1 订阅了
if (dep.subs === undefined) {
// 不会执行
dep.subs = newLink;
} else {
// 这次执行这个
const oldTail = dep.subsTail!;
newLink.prevSub = oldTail;
oldTail.nextSub = newLink;
}
// 更新尾部指针
sub.depsTail = newLink;
dep.subsTail = newLink;
}
至此,所有的依赖收集完成,最终的依赖关系图如下:
依赖变化触发更新
count1(2);
当依赖的值发生变化后,首先会获取当前依赖的第一个订阅this.subs,根据关系图可知,也就是这个红色箭头执行的 newLink
function signalGetterSetter(...value) {
if (value.length) {
if (this.currentValue !== (this.currentValue = value[0])) {
const subs = this.subs;
if (subs !== void 0) {
propagate(subs);
// ...
processEffectNotifications();
}
}
}
// ...
}
接着在propagate(subs)函数中遍历这个 dep1 依赖所有的 sub
//! 删除了部分无关代码
function propagate(current: Link): void {
let next = current.nextSub;
let targetFlag = SubscriberFlags.Dirty;
// ...
top: do {
const sub = current.sub;
const subFlags = sub.flags; // Effect
let shouldNotify = false;
if (
!(
subFlags &
(SubscriberFlags.Tracking |
SubscriberFlags.Recursed |
// Dirty | PendingComputed | PendingEffect
SubscriberFlags.Propagated)
)
) {
// Effect | Dirty | Notified
sub.flags = subFlags | targetFlag | SubscriberFlags.Notified;
shouldNotify = true;
}
// ...
if (shouldNotify) {
const subSubs = (sub as Dependency).subs;
if (subSubs !== undefined) {
// ... 嵌套的effect,当前的单元测试不会执行到这里
}
if (subFlags & SubscriberFlags.Effect) {
notifyBuffer[notifyBufferLength++] = sub;
}
}
// ...
if ((current = next!) !== undefined) {
next = current.nextSub;
// 现在这个单元测试没有 branchDepth
targetFlag = branchDepth
? SubscriberFlags.PendingComputed
: SubscriberFlags.Dirty;
continue;
}
// ...
break;
} while (true);
}
第一次 do while :sub1 后如下,并且targetFlag = SubscriberFlags.Dirty
第二次 do while :sub2 后如下
现在我们就收集到了 dep1 的两个订阅,既notifyBuffer->[sub1,sub2],然后通过这个函数processEffectNotifications和notifyEffect开始处理notifyBuffer中的订阅
function processEffectNotifications(): void {
while (notifyIndex < notifyBufferLength) {
const effect = notifyBuffer[notifyIndex]!;
notifyBuffer[notifyIndex++] = undefined;
if (!notifyEffect(effect)) {
effect.flags &= ~SubscriberFlags.Notified;
}
}
notifyIndex = 0;
notifyBufferLength = 0;
}
function notifyEffect(e: Effect): boolean {
const flags = e.flags;
if (
flags & SubscriberFlags.Dirty ||
// 如果是computed在effect使用就会走这个分支,当前不会
(flags & SubscriberFlags.PendingComputed && updateDirtyFlag(e, flags))
) {
const prevSub = activeSub;
activeSub = e;
startTracking(e);
try {
// 执行副作用函数,并重新依赖收集
e.fn();
} finally {
activeSub = prevSub;
endTracking(e);
}
}
// ...
return true;
}
在执行副作用函数之前,会执行startTracking函数,将该 sub 的 depsTail 置为 undefined,表示需要重新依赖收集,并且取消Notified 和Dirty这两个标签,新增一个Tracking标签
function startTracking(sub: Subscriber): void {
sub.depsTail = undefined;
sub.flags =
(sub.flags &
~(
SubscriberFlags.Notified |
SubscriberFlags.Recursed |
// Dirty | PendingComputed | PendingEffect
SubscriberFlags.Propagated
)) |
SubscriberFlags.Tracking;
}
现在e.fn()重新执行fn1
function fn1() {
console.log(`effect1-> count1 is: ${count1()}`);
console.log(`effect1-> count2 is: ${count2()}`);
}
当执行到count1(),重新 link(dep1,sub1)
function signalGetterSetter<T>(this: Signal<T>, ...value: [T]): T | void {
if (value.length) {
// ...
} else {
if (activeSub !== undefined) {
link(this, activeSub);
}
return this.currentValue;
}
}
function link(dep: Dependency, sub: Subscriber): Link | undefined {
// sub.depsTail -> undefined
const currentDep = sub.depsTail;
// ...
// nextDep -> sub.deps -> dep1
const nextDep = currentDep !== undefined ? currentDep.nextDep : sub.deps;
if (nextDep !== undefined && nextDep.dep === dep) {
// sub.depsTail -> dep1
sub.depsTail = nextDep;
return;
}
// ...
// 复用以前的节点,不会执行这个
return linkNewDep(dep, sub, nextDep, currentDep);
}
link 后如下所示
然后count2()进行依赖收集link(dep2,sub1),link 后如图所示
然后这个 fn1 就执行完了,并且也重新完成了新的依赖收集,然后使用
endTracking(sub1)做清理,取消Tracking标签
function endTracking(sub: Subscriber): void {
// ...
// 取消 Tracking 标签
sub.flags &= ~SubscriberFlags.Tracking;
}
接下来处理 notifyBuffer 的的 sub2,同样也会在执行副作用函数之前,会执行startTracking函数,将该 sub2 的 depsTail 置为 undefined,表示需要重新依赖收集,并且取消Notified 和Dirty这两个标签,新增一个Tracking标签
然后重新执行
fn2,重新进行依赖收集,先收集count1(),既link(dep1,sub2)
function link(dep: Dependency, sub: Subscriber): Link | undefined {
//sub2.depsTail -> undefined
const currentDep = sub.depsTail;
// ...
// nextDep -> sub2.deps -> dep1
const nextDep = currentDep !== undefined ? currentDep.nextDep : sub.deps;
if (nextDep !== undefined && nextDep.dep === dep) {
// sub.depsTail -> dep1
sub.depsTail = nextDep;
return;
}
// ...
// 复用以前的节点,不会执行这个
return linkNewDep(dep, sub, nextDep, currentDep);
}
link 后如图所示:
然后收集count2(),既link(dep2,sub2),link 后如图所示:
现在这个 fn12 也执行完了,并且也重新完成了新的依赖收集,同样也需要使用
endTracking(sub2)做清理,取消Tracking标签。如图所示:
到现在count1(2)就正在的完成了。
后面的count2(200),其实也是和count1(2)一模一样的流程