solid.js 源码分析(三)- createEffect
createEffect 源码分析
createEffect
会创建一个 computation ,当在函数体内执行 getter 函数,会自动收集相关依赖。当依赖更新时,会重新运行该函数。
下面我们将使用这段代码对源码进行分析:
const [count, setCount] = createSignal(0);
createEffect(() => {
console.log("count =", count());
});
setCount(count() + 1);
依赖收集
createEffect
首先我们来看下 createEffect
的声明:
export interface BaseOptions {
name?: string;
}
export interface EffectOptions extends BaseOptions {}
// Also similar to OnEffectFunction
export type EffectFunction<Prev, Next extends Prev = Prev> = (v: Prev) => Next;
export function createEffect<Next>(fn: EffectFunction<undefined | NoInfer<Next>, Next>): void;
export function createEffect<Next, Init = Next>(
fn: EffectFunction<Init | Next, Next>,
value: Init,
options?: EffectOptions
): void;
可以看到 createEffect
可以传递三个参数,分别是 fn、value 和 options,没有返回值。
- fn:副作用函数,当依赖项更新时,会重新执行
- value:初始值,创建 compulation 对象时会作为初始值
- options
- name:compulation 对象名称,开发环境使用,生产环境会被移除
接下来看下具体实现:
let Listener: Computation<any> | null = null;
let Updates: Computation<any>[] | null = null;
let Effects: Computation<any>[] | null = null;
let runEffects = runQueue;
function runQueue(queue: Computation<any>[]) {
for (let i = 0; i < queue.length; i++) runTop(queue[i]);
}
function runUserEffects(queue: Computation<any>[]) {
let i,
userLength = 0;
for (i = 0; i < queue.length; i++) {
const e = queue[i];
if (!e.user) runTop(e);
else queue[userLength++] = e;
}
if (sharedConfig.context) setHydrateContext();
for (i = 0; i < userLength; i++) runTop(queue[i]);
}
export function createEffect<Next, Init>(
fn: EffectFunction<Init | Next, Next>,
value?: Init,
options?: EffectOptions
): void {
runEffects = runUserEffects;
const c = createComputation(fn, value!, false, STALE, "_SOLID_DEV_" ? options : undefined),
s = SuspenseContext && lookup(Owner, SuspenseContext.id);
if (s) c.suspense = s;
c.user = true;
Effects ? Effects.push(c) : updateComputation(c);
}
首先将 runUserEffects
函数赋值给 runEffects
,用来指定用户副作用函数的调度流程。
然后会调用 createComputation
函数创建 computation 对象,这个 computation 对象就是 readSignal 中的 Listener,当 readSignal 函数执行时会被被添加 state 的 observers 数组中。
我们可以暂时忽略 suspense 相关逻辑,这里只需要看响应式处理相关的内容。
创建完 computation 对象后,会将 computation 对象的 user 属性设置为 true,说明这是用户传入的 effect 函数。
在 solid.js 中,凡是需要收集依赖并在依赖变化时进行更新的操作,都会被描述成一个 Computation。
接着会判断 Effects 是否存在,如果存在将 computation 对象添加到 Effects 数组中,否则调用 updateComputation
函数进行更新。
createComputation
在 createEffect 创建 computation 对象,传入以下参数:
const STALE = 1;
const c = createComputation(fn, value!, false, STALE, "_SOLID_DEV_" ? options : undefined)
第 3 个参数为 false,即 pure 属性设置为 false,然后第 4 个参数 state 设置为 1。
function createComputation<Next, Init = unknown>(
fn: EffectFunction<Init | Next, Next>,
init: Init,
pure: boolean,
state: number = STALE,
options?: EffectOptions
): Computation<Init | Next, Next> {
const c: Computation<Init | Next, Next> = {
fn,
state: state,
updatedAt: null,
owned: null,
sources: null,
sourceSlots: null,
cleanups: null,
value: init,
owner: Owner,
context: null,
pure
};
if (Transition && Transition.running) {
c.state = 0;
c.tState = state;
}
if (Owner === null)
"_SOLID_DEV_" &&
console.warn(
"computations created outside a `createRoot` or `render` will never be disposed"
);
else if (Owner !== UNOWNED) {
if (Transition && Transition.running && (Owner as Memo<Init, Next>).pure) {
if (!(Owner as Memo<Init, Next>).tOwned) (Owner as Memo<Init, Next>).tOwned = [c];
else (Owner as Memo<Init, Next>).tOwned!.push(c);
} else {
if (!Owner.owned) Owner.owned = [c];
else Owner.owned.push(c);
}
if ("_SOLID_DEV_")
c.name =
(options && options.name) ||
`${(Owner as Computation<any>).name || "c"}-${
(Owner.owned || (Owner as Memo<Init, Next>).tOwned!).length
}`;
}
if (ExternalSourceFactory) {
const [track, trigger] = createSignal<void>(undefined, { equals: false });
const ordinary = ExternalSourceFactory(c.fn, trigger);
onCleanup(() => ordinary.dispose());
const triggerInTransition: () => void = () =>
startTransition(trigger).then(() => inTransition.dispose());
const inTransition = ExternalSourceFactory(c.fn, triggerInTransition);
c.fn = x => {
track();
return Transition && Transition.running ? inTransition.track(x) : ordinary.track(x);
};
}
return c;
}
createComputation 函数的主要的功能就是创建 computation 对象 c,并将它返回。
updateComputation
export function createEffect<Next, Init>(
fn: EffectFunction<Init | Next, Next>,
value?: Init,
options?: EffectOptions
): void {
runEffects = runUserEffects;
const c = createComputation(fn, value!, false, STALE, "_SOLID_DEV_" ? options : undefined),
s = SuspenseContext && lookup(Owner, SuspenseContext.id);
if (s) c.suspense = s;
c.user = true;
Effects ? Effects.push(c) : updateComputation(c);
}
首次执行时,Effects 数组肯定是不存在的,所以会调用 updateComputation 函数,并将 computation 对象传入。
function updateComputation(node: Computation<any>) {
if (!node.fn) return;
cleanNode(node);
const owner = Owner,
listener = Listener,
time = ExecCount;
Listener = Owner = node;
runComputation(
node,
Transition && Transition.running && Transition.sources.has(node as Memo<any>)
? (node as Memo<any>).tValue
: node.value,
time
);
if (Transition && !Transition.running && Transition.sources.has(node as Memo<any>)) {
queueMicrotask(() => {
runUpdates(() => {
Transition && (Transition.running = true);
runComputation(node, (node as Memo<any>).tValue, time);
}, false);
});
}
Listener = listener;
Owner = owner;
}
首先判断 fn 是否存在,如果不存在,直接返回。然后执行 cleanNode 逻辑。
function cleanNode(node: Owner) {
let i;
if ((node as Computation<any>).sources) {
while ((node as Computation<any>).sources!.length) {
const source = (node as Computation<any>).sources!.pop()!,
index = (node as Computation<any>).sourceSlots!.pop()!,
obs = source.observers;
if (obs && obs.length) {
const n = obs.pop()!,
s = source.observerSlots!.pop()!;
if (index < obs.length) {
n.sourceSlots![s] = index;
obs[index] = n;
source.observerSlots![index] = s;
}
}
}
}
if (Transition && Transition.running && (node as Memo<any>).pure) {
if ((node as Memo<any>).tOwned) {
for (i = 0; i < (node as Memo<any>).tOwned!.length; i++)
cleanNode((node as Memo<any>).tOwned![i]);
delete (node as Memo<any>).tOwned;
}
reset(node as Computation<any>, true);
} else if (node.owned) {
for (i = 0; i < node.owned.length; i++) cleanNode(node.owned[i]);
node.owned = null;
}
if (node.cleanups) {
for (i = 0; i < node.cleanups.length; i++) node.cleanups[i]();
node.cleanups = null;
}
if (Transition && Transition.running) (node as Computation<any>).tState = 0;
else (node as Computation<any>).state = 0;
node.context = null;
"_SOLID_DEV_" && delete node.sourceMap;
}
在 cleanNode 中,首次使用 createEffect,大部分逻辑都不会走到, 这时 computation 对象的 sources、owned 都为空,不过这时会触发 (node as Computation<any>).state = 0;
, 将原本 computation 对象的 state 设置为 0,原本为 1。
然后定义 owner
、listener
缓存已经存在的 Owner
、Listener
,这时的 owner
、listener
都为空。
接下来的操作比较关键,将 node
赋值给 Listener
、Owner
,这时如果调用 readSignal 时,Listener 是存在的,即当前正在执行的 Computation 对象。
然后会调用 runComputation
方法,执行用户传入的副作用函数。
function updateComputation(node: Computation<any>) {
if (!node.fn) return;
cleanNode(node);
const owner = Owner,
listener = Listener,
time = ExecCount;
Listener = Owner = node;
runComputation(
node,
Transition && Transition.running && Transition.sources.has(node as Memo<any>)
? (node as Memo<any>).tValue
: node.value,
time
);
if (Transition && !Transition.running && Transition.sources.has(node as Memo<any>)) {
queueMicrotask(() => {
runUpdates(() => {
Transition && (Transition.running = true);
runComputation(node, (node as Memo<any>).tValue, time);
}, false);
});
}
Listener = listener;
Owner = owner;
}
此时 Transition 并不存在,跳过下面这段逻辑,最后再还原 Listener、Owner。
runComputation
function runComputation(node: Computation<any>, value: any, time: number) {
let nextValue;
try {
nextValue = node.fn(value);
} catch (err) {
if (node.pure) Transition && Transition.running ? (node.tState = STALE) : (node.state = STALE);
handleError(err);
}
if (!node.updatedAt || node.updatedAt <= time) {
if (node.updatedAt != null && "observers" in (node as Memo<any>)) {
writeSignal(node as Memo<any>, nextValue, true);
} else if (Transition && Transition.running && node.pure) {
Transition.sources.add(node as Memo<any>);
(node as Memo<any>).tValue = nextValue;
} else node.value = nextValue;
node.updatedAt = time;
}
}
首先定义 nextValue
变量,用来存储最新计算的值。
接下来调用 node.fn
函数,计算值并赋值给 nextValue
变量。这里的 node.fn
就是我们使用 createEffect
传入的自定义函数。
获取到最新值后,判断 node.updateAt
是否不存在,或者 node.updatedAt < time
。
首次创建时,updateAt 属性并不存在,这时 Transition
也不存在,所以这里只是进行赋值操作,将计算后的最新值赋值给 node.value
。
然后给 node.updateAt
赋值为 time,这里的 time 是调用 runComputation
时传入的,这时的 time
为默认值 ExecCount
,它的值是 0。
下面我们继续看执行 node.fn
的过程,当执行到 node.fn
时,会调用我们传入的自定义函数。
createEffect(() => {
console.log("count =", count());
});
我们在函数中使用了名为 count 的 getter 函数,当调用 count 时,会调用 getter 函数,即 readSignal
。
// Internal
export function readSignal(this: SignalState<any> | Memo<any>) {
const runningTransition = Transition && Transition.running;
if (
(this as Memo<any>).sources &&
((!runningTransition && (this as Memo<any>).state) ||
(runningTransition && (this as Memo<any>).tState))
) {
if (
(!runningTransition && (this as Memo<any>).state === STALE) ||
(runningTransition && (this as Memo<any>).tState === STALE)
)
updateComputation(this as Memo<any>);
else {
const updates = Updates;
Updates = null;
runUpdates(() => lookUpstream(this as Memo<any>), false);
Updates = updates;
}
}
if (Listener) {
const sSlot = this.observers ? this.observers.length : 0;
if (!Listener.sources) {
Listener.sources = [this];
Listener.sourceSlots = [sSlot];
} else {
Listener.sources.push(this);
Listener.sourceSlots!.push(sSlot);
}
if (!this.observers) {
this.observers = [Listener];
this.observerSlots = [Listener.sources.length - 1];
} else {
this.observers.push(Listener);
this.observerSlots!.push(Listener.sources.length - 1);
}
}
if (runningTransition && Transition!.sources.has(this)) return this.tValue;
return this.value;
}
这段代码其实我们之前已经分析过,它会返回当前 state 的 value 值,即我们定义的属性值。
不过当时 Listener
并不存在,现在情况则不同,当我们调用 updateComputation
方法时,Listener 已经被赋值为 node
,即 computation
对象。
Listener = Owner = node;
目前的 Transition 仍然是不存在的,我们直接来看 Listerner
相关逻辑。
// Internal
export function readSignal(this: SignalState<any> | Memo<any>) {
// ...
if (Listener) {
const sSlot = this.observers ? this.observers.length : 0;
if (!Listener.sources) {
Listener.sources = [this];
Listener.sourceSlots = [sSlot];
} else {
Listener.sources.push(this);
Listener.sourceSlots!.push(sSlot);
}
if (!this.observers) {
this.observers = [Listener];
this.observerSlots = [Listener.sources.length - 1];
} else {
this.observers.push(Listener);
this.observerSlots!.push(Listener.sources.length - 1);
}
}
if (runningTransition && Transition!.sources.has(this)) return this.tValue;
return this.value;
}
这里的 this 还是 state,即 signalState 对象。
首先判断 observers
数组是否存在,很明显此时 observers
为空,我们并没有为其赋过值,所以这里的 sSlot
是 0。
然后判断 Listener.sources
是否存在:
- 如果
Listener.sources
不存在,创建空数组,并分别为其赋值为 this(signalState)和 sSlot。 - 如果
Listener.sourcese
存在,在数组中追加值。
继续判断 this.observers
是否存在:
- 如果
this.observers
不存在- 创建空数组,赋值为
Listener
,即 computation 对象,赋值给this.observers
; - 创建空数组,赋值为
Listener.sources.length - 1
,此时值为 0;
- 创建空数组,赋值为
- 如果
this.observers
存储,追加值
执行完这部分代码,computation 和 signalState 已经建立起双向联系:
- signalState 的 observers 数组中,存储的是 computation;
- computation 的 sources 数组中,存储的是 signalState。
最后返回 this.value
,这时控制台就可以打印出 count = 0
。
更新流程
writeSignal
代码继续执行,接下来执行更新操作。
setCount(count() + 1);
首先调用 getter 函数获取当前值,继续 writeSignal 函数,不过此时 Listenenr
已经设置为空,所以只是返回当前值。
updateComputation 执行完 runComputation,会把缓存的 listener 赋值给 Listener,owner 赋值给 Owner。
所以我们得到数值 1 并传递给 setCount
函数,即 setter 函数,然后调用 writeSignal。
export function createSignal<T>(value?: T, options?: SignalOptions<T>): Signal<T | undefined> {
// ...
const setter: Setter<T | undefined> = (value?: unknown) => {
if (typeof value === "function") {
if (Transition && Transition.running && Transition.sources.has(s)) value = value(s.tValue);
else value = value(s.value);
}
return writeSignal(s, value);
};
return [readSignal.bind(s), setter];
}
由于这里的 value 并不是 function,会直接调用 writeSignal
函数。
export function writeSignal(node: SignalState<any> | Memo<any>, value: any, isComp?: boolean) {
let current =
Transition && Transition.running && Transition.sources.has(node) ? node.tValue : node.value;
if (!node.comparator || !node.comparator(current, value)) {
if (Transition) {
const TransitionRunning = Transition.running;
if (TransitionRunning || (!isComp && Transition.sources.has(node))) {
Transition.sources.add(node);
node.tValue = value;
}
if (!TransitionRunning) node.value = value;
} else node.value = value;
if (node.observers && node.observers.length) {
runUpdates(() => {
for (let i = 0; i < node.observers!.length; i += 1) {
const o = node.observers![i];
const TransitionRunning = Transition && Transition.running;
if (TransitionRunning && Transition!.disposed.has(o)) continue;
if ((TransitionRunning && !o.tState) || (!TransitionRunning && !o.state)) {
if (o.pure) Updates!.push(o);
else Effects!.push(o);
if ((o as Memo<any>).observers) markDownstream(o as Memo<any>);
}
if (TransitionRunning) o.tState = STALE;
else o.state = STALE;
}
if (Updates!.length > 10e5) {
Updates = [];
if ("_SOLID_DEV_") throw new Error("Potential Infinite Loop Detected.");
throw new Error();
}
}, false);
}
}
return value;
}
此时 Transition 依旧不存在,会将 value 值赋值给 node.value
,此时 node.value
已经变为 1。
接下来继续执行,此时 node.observers
是存在的。使用 runUpdates
函数包括匿名函数,runUpdates 是调度逻辑,我们后面再来分析。
首先对 observers 数组进行遍历,获取到 computation 对象。
因为 Transition 并不存在,但是 computation 的 state 为 0, 所以会命中以下逻辑:
在 updateComputation 的 cleanNode 函数中将 state 修改为 0。
const STALE = 1;
if ((TransitionRunning && !o.tState) || (!TransitionRunning && !o.state)) {
if (o.pure) Updates!.push(o);
else Effects!.push(o);
if ((o as Memo<any>).observers) markDownstream(o as Memo<any>);
}
if (TransitionRunning) o.tState = STALE;
else o.state = STALE;
由于此时 pure 属性为 false,所以会将 computation 对象添加到 Effects 数组中,然后再将 state 的值重新修改为 1。
runUpdates
runUpdates 是任务调度相关逻辑:
let ExecCount = 0;
function runUpdates<T>(fn: () => T, init: boolean) {
if (Updates) return fn();
let wait = false;
if (!init) Updates = [];
if (Effects) wait = true;
else Effects = [];
ExecCount++;
try {
const res = fn();
completeUpdates(wait);
return res;
} catch (err) {
if (!Updates) Effects = null;
handleError(err);
}
}
判断 Updates 是否存在,如果存在,直接返回函数执行。
定义 wait 变量,赋值 false,!init
为真,将 Updates 赋值为空数组。此时 Effects 并不存在,赋值为空数组,ExecCount
变量自增,此时为 1。
然后执行函数内部逻辑,将 computation 添加到 Effects 数组中。
const res = fn();
completeUpdates(wait);
最后调用 completeUpdates
函数完成更新。
completeUpdates
function completeUpdates(wait: boolean) {
if (Updates) {
if (Scheduler && Transition && Transition.running) scheduleQueue(Updates);
else runQueue(Updates);
Updates = null;
}
if (wait) return;
let res;
if (Transition && Transition.running) {
if (Transition.promises.size || Transition.queue.size) {
Transition.running = false;
Transition.effects.push.apply(Transition.effects, Effects!);
Effects = null;
setTransPending(true);
return;
}
// finish transition
const sources = Transition.sources;
const disposed = Transition.disposed;
res = Transition.resolve;
for (const e of Effects!) {
"tState" in e && (e.state = e.tState!);
delete e.tState;
}
Transition = null;
runUpdates(() => {
for (const d of disposed) cleanNode(d);
for (const v of sources) {
v.value = v.tValue;
if ((v as Memo<any>).owned) {
for (let i = 0, len = (v as Memo<any>).owned!.length; i < len; i++)
cleanNode((v as Memo<any>).owned![i]);
}
if ((v as Memo<any>).tOwned) (v as Memo<any>).owned = (v as Memo<any>).tOwned!;
delete v.tValue;
delete (v as Memo<any>).tOwned;
(v as Memo<any>).tState = 0;
}
setTransPending(false);
}, false);
}
const e = Effects!;
Effects = null;
if (e!.length) runUpdates(() => runEffects(e), false);
else if ("_SOLID_DEV_") globalThis._$afterUpdate && globalThis._$afterUpdate();
if (res) res();
}
首先判断 Updates 是否存在,这里确实存在,不过它是一个空数组。
function runQueue(queue: Computation<any>[]) {
for (let i = 0; i < queue.length; i++) runTop(queue[i]);
}
if (Updates) {
if (Scheduler && Transition && Transition.running) scheduleQueue(Updates);
else runQueue(Updates);
Updates = null;
}
然后判断 Scheduler 和 Transition,这里都是不存在的,然后会调用 runQueue
。因为这里是空数组,暂时跳过这部分逻辑,执行完毕后,会将 Updates 重新设置为 null。
继续执行,判断 wait 变量,值为 false 跳过,Transition 相关逻辑跳过,然后就只剩下这段代码。
const e = Effects!;
Effects = null;
if (e!.length) runUpdates(() => runEffects(e), false);
else if ("_SOLID_DEV_") globalThis._$afterUpdate && globalThis._$afterUpdate();
if (res) res();
将 Effects 赋值给 e,然后将 Effects 设置为空。判断 e 的长度,继续调用 runUpdates
函数,传入 runEffects(e)
,执行 Effect。
runEffects
export function createEffect<Next, Init>(
fn: EffectFunction<Init | Next, Next>,
value?: Init,
options?: EffectOptions
): void {
runEffects = runUserEffects;
const c = createComputation(fn, value!, false, STALE, "_SOLID_DEV_" ? options : undefined),
s = SuspenseContext && lookup(Owner, SuspenseContext.id);
if (s) c.suspense = s;
c.user = true;
Effects ? Effects.push(c) : updateComputation(c);
}
这里的 runEffects 函数其实是 runUserEffects,在调用 createEffect 的时候,就已经把 runUserEffects
赋值给 runEffects
。
function runUserEffects(queue: Computation<any>[]) {
let i,
userLength = 0;
for (i = 0; i < queue.length; i++) {
const e = queue[i];
if (!e.user) runTop(e);
else queue[userLength++] = e;
}
if (sharedConfig.context) setHydrateContext();
for (i = 0; i < userLength; i++) runTop(queue[i]);
}
首先循环遍历 effects queue,其实我们这里只有一个 effect 对象,即 computation 对象。
因为这里的 computation 是用户创建的 computation,所以不会立即调用 runTop 去执行 computation,而是将 user computation 缓存起来,优先执行系统创建的 computation。
这里的逻辑非常巧妙,我们可以举个例子。假设我们有多个 computation,既包括用户创建的 computation,也有系统创建的。
runUserEffects([
{
user: false,
text: "computation01"
},
{
user: true,
text: "user computation01"
},
{
user: true,
text: "user computation02"
},
{
user: false,
text: "computation02"
},
{
user: true,
text: "user computation03"
}
]);
function runUserEffects(queue) {
let i;
let userLength = 0;
for (i = 0; i < queue.length; i++) {
const e = queue[i];
if (!e.user) console.log(e.text);
else queue[userLength++] = e;
}
const userEffects = queue.slice(0, userLength);
console.log(userEffects);
for (let i = 0; i < userLength; i++) {
console.log(queue[i].text);
}
// computation01
// computation02
// [
// { user: true, text: 'user computation01' },
// { user: true, text: 'user computation02' },
// { user: true, text: 'user computation03' }
// ]
// user computation01
// user computation02
// user computation03
}
可以看到,如果是用户 computation,会以 userLength 重新对数组进行赋值,这里的 i 肯定是大于 userLength,所以不会有问题。
然后判断 sharedConfig.context
是否存在,这里当然是不存在的,跳过。
最后再依次执行从 0 到 userLength 区间内的 user computation,使用 runTop
函数去执行每个 computation。
所以这里 computation 的执行是有优先级的,首先会执行系统创建的 computation,然后才会执行 user computation。
runTop
此时 Transition 不存在, node.state 的值是 1,也不存在 suspense 属性,所以会跳过相关逻辑。
function runTop(node: Computation<any>) {
const runningTransition = Transition && Transition.running;
if ((!runningTransition && node.state === 0) || (runningTransition && node.tState === 0)) return;
if (
(!runningTransition && node.state === PENDING) ||
(runningTransition && node.tState === PENDING)
)
return lookUpstream(node);
if (node.suspense && untrack(node.suspense.inFallback!))
return node!.suspense.effects!.push(node!);
// ...
}
跳过上述判断逻辑后,开始正式执行更新逻辑。
function runTop(node: Computation<any>) {
// ...
const ancestors = [node];
while (
(node = node.owner as Computation<any>) &&
(!node.updatedAt || node.updatedAt < ExecCount)
) {
if (runningTransition && Transition!.disposed.has(node)) return;
if ((!runningTransition && node.state) || (runningTransition && node.tState))
ancestors.push(node);
}
for (let i = ancestors.length - 1; i >= 0; i--) {
node = ancestors[i];
if (runningTransition) {
let top = node,
prev = ancestors[i + 1];
while ((top = top.owner as Computation<any>) && top !== prev) {
if (Transition!.disposed.has(top)) return;
}
}
if (
(!runningTransition && node.state === STALE) ||
(runningTransition && node.tState === STALE)
) {
updateComputation(node);
} else if (
(!runningTransition && node.state === PENDING) ||
(runningTransition && node.tState === PENDING)
) {
const updates = Updates;
Updates = null;
runUpdates(() => lookUpstream(node, ancestors[0]), false);
Updates = updates;
}
}
}
首先定义数组 ancestores,设置第一个元素为当前 node,即 computation 对象。
然后开启一个 while 循环,不断将 node.owner 赋值给 node,判断是否存在,如果存在并且符合连带条件,会将 node 添加到 ancestors 数组中。不过此时这里的 node .owner 并不存在,所以不会进行添加操作。
代码继续执行,定义一个 for 循环,从后往前执行,遍历 ancestors 数组。
for (let i = ancestors.length - 1; i >= 0; i--) {
node = ancestors[i];
if (runningTransition) {
let top = node,
prev = ancestors[i + 1];
while ((top = top.owner as Computation<any>) && top !== prev) {
if (Transition!.disposed.has(top)) return;
}
}
if (
(!runningTransition && node.state === STALE) ||
(runningTransition && node.tState === STALE)
) {
updateComputation(node);
} else if (
(!runningTransition && node.state === PENDING) ||
(runningTransition && node.tState === PENDING)
) {
const updates = Updates;
Updates = null;
runUpdates(() => lookUpstream(node, ancestors[0]), false);
Updates = updates;
}
}
由于 runnintTransition 不存在,并且 node.state 为 1 ,等于 STALE
,所以会执行这行代码。
updateComputation(node);
updateComputation
所以,现在又回到这段代码(ps:是不是感觉好绕,我也是这么想的),开始更新 computation:
function updateComputation(node: Computation<any>) {
if (!node.fn) return;
cleanNode(node);
const owner = Owner,
listener = Listener,
time = ExecCount;
Listener = Owner = node;
runComputation(
node,
Transition && Transition.running && Transition.sources.has(node as Memo<any>)
? (node as Memo<any>).tValue
: node.value,
time
);
if (Transition && !Transition.running && Transition.sources.has(node as Memo<any>)) {
queueMicrotask(() => {
runUpdates(() => {
Transition && (Transition.running = true);
runComputation(node, (node as Memo<any>).tValue, time);
}, false);
});
}
Listener = listener;
Owner = owner;
}
这里的执行逻辑和我们之前分析依赖收集基本一致。
不同点在于这里的 time 变量已经变为 2,还有一个不同发生在 cleanNode
方法中。
function cleanNode(node: Owner) {
let i;
if ((node as Computation<any>).sources) {
while ((node as Computation<any>).sources!.length) {
const source = (node as Computation<any>).sources!.pop()!,
index = (node as Computation<any>).sourceSlots!.pop()!,
obs = source.observers;
if (obs && obs.length) {
const n = obs.pop()!,
s = source.observerSlots!.pop()!;
if (index < obs.length) {
n.sourceSlots![s] = index;
obs[index] = n;
source.observerSlots![index] = s;
}
}
}
}
// ...
else (node as Computation<any>).state = 0;
node.context = null;
"_SOLID_DEV_" && delete node.sourceMap;
}
这里的 computation
是存在 sources
数组的,所以会对 sources
数组及其依赖的 observers
数组进行清理,解除两者的依赖关系。然后再将 computation
的 state
设置为 0。
然后再执行 runComputation
方法,重新建立起依赖关系。
runComputation
function runComputation(node: Computation<any>, value: any, time: number) {
let nextValue;
try {
nextValue = node.fn(value);
} catch (err) {
if (node.pure) Transition && Transition.running ? (node.tState = STALE) : (node.state = STALE);
handleError(err);
}
if (!node.updatedAt || node.updatedAt <= time) {
if (node.updatedAt != null && "observers" in (node as Memo<any>)) {
writeSignal(node as Memo<any>, nextValue, true);
} else if (Transition && Transition.running && node.pure) {
Transition.sources.add(node as Memo<any>);
(node as Memo<any>).tValue = nextValue;
} else node.value = nextValue;
node.updatedAt = time;
}
}
定义变量 nextValue,执行 node.fn
,将返回值赋值给 nextValue
。这里的 node.fn
就是我们的副作用函数:
createEffect(() => {
console.log("count =", count());
});
执行副作用函数会重新进行取值操作,收集依赖,需要注意的是这里 souces
数组和 observers
都是空数组,所以会执行 push
新增逻辑。最后返回新值。
// Internal
export function readSignal(this: SignalState<any> | Memo<any>) {
// ...
if (Listener) {
const sSlot = this.observers ? this.observers.length : 0;
if (!Listener.sources) {
Listener.sources = [this];
Listener.sourceSlots = [sSlot];
} else {
Listener.sources.push(this);
Listener.sourceSlots!.push(sSlot);
}
if (!this.observers) {
this.observers = [Listener];
this.observerSlots = [Listener.sources.length - 1];
} else {
this.observers.push(Listener);
this.observerSlots!.push(Listener.sources.length - 1);
}
}
// ...
return this.value;
}
此时控制台会打印出:
count = 1
因为此时 updateAt 属性存在并小于 time,此时为 2,所以会触发下面逻辑。
if (node.updatedAt != null && "observers" in (node as Memo<any>)) {
writeSignal(node as Memo<any>, nextValue, true);
} else if (Transition && Transition.running && node.pure) {
Transition.sources.add(node as Memo<any>);
(node as Memo<any>).tValue = nextValue;
} else node.value = nextValue;
node.updatedAt = time;
然后会回到 updateComputation
,将 Listener 和 Owner 重新赋值为缓存的 listener 和 owner。
继续回退,直至清空函数调用栈,代码执行完毕。
总结
我们已经完成对 solid.js 的依赖收集和更新过程分析。这里对执行过程做一下总结。
首先是依赖收集过程,当我们使用 createEffect
时:
- 首先会将
runUserEffects
赋值给runEffects
- 然后会调用
createComputation
创建computation
对象 - 标识
computation
为用户computation
- 接着会调用
updateComputation
方法- 清洗
node(computation)
,设置computation
对象的state
为 0 - 缓存上次的
Owner
和Listener
,设置time
属性值,将node(computation)
, 赋值给Listener
和Owner
- 调用
runComputation
方法- 定义
nextValue
属性 - 执行用户传入的副作用函数,此时会执行
getter
,获取最新值,赋值给nextValue
- 因为此时
Listener(computation)
已经存在,所以会和siganlState
建立起依赖关系; - 只有这样我们才可以在设置响应式数据时,获取依赖当前数据的观察者数据,进行更新。
- 因为此时
- 因为此时
Transition
和node.updatedAt
都不存在,所以会把最新值赋值给computation.value
,这里也是undefined
- 设置
node
的updatedAt
属性
- 定义
- 还原
Listerner
和Owner
- 清洗
然后是更新流程,当我们使用 setCount(count() + 1)
设置值时:
- 首先使用
count()
调用getter(readSignal)
获取最新值,然后将结果加 1 传入setCount
- 调用
setCount
会触发setter
,因为我们传入的value
是原始值,所以会直接调用writeSignal
- 将传入的新值赋值给
node.value
,此时node
的值已经是最新值 - 因为此时
node(signleSignal)
存在observers(computation)
数组,所以会执行更新流程 - 使用
runUpdates
传入处理computation
数组的匿名函数,最终会将computation
添加到Effects
数组中- 设置
wait
变量,赋值为false
,初始化Effects
数组; - 全局变量
ExecCount
自增; - 调用传入的匿名函数,将
computation
添加到Effects
数组中,并且将computation
的值重新设置为 1 - 调用
completeUpdates
完成更新,传入变量wait
,此时值为false
- 缓存
Effects
数组,赋值给变量e
,Effects
重新设置为null
- 继续调用
runUpdates
传入匿名函数内部调用runEffects
,参数为e
- 同样设置
wait
变量,赋值为false
,初始化Effects
数组,数组为空,ExecCount
变量自增 - 调用传入的匿名函数,触发
runEffects
- 处理
effects
数组,优先执行系统定义的effect
,其次执行用户传入的effect
- 调用
runTop
方法执行effect
- 定义
ancestors
,存储effect
- 如果
effect(computation)
存在owner
属性,添加到ancestors
数组中 - 从后向前遍历
ancestors
数组,因为此时transition
不存在且node.state
为 1 - 调用
updateComputation
方法更新computation
,具体逻辑和之前分析依赖收集基本一致。不过在执行cleanNode
方法时,由于此时存在依赖关系,所以会清除依赖关系,然后在执行runComutation
过程中重新建立依赖关系。
- 定义
- 处理
- 调用
completeUpdates
完成更新,传入变量wait
,此时值为false
- 此时 Effects 数组为空,不会执行更新流程,返回
- 同样设置
- 副作用函数执行完毕,返回
- 缓存
- 更新完成,返回
- 设置
- 返回当前 value,即我们传入的值
- 将传入的新值赋值给
简单概括一下就是:
当使用 createEffect
时,会创建 computation
对象,如果内部存在响应式数据 singleState
,两者会建立依赖关系。
- 将
computation
对象添加到singleState
的observers
数组中 - 将
singleState
对象添加到computation
的sources
数组中
当我们设置响应式数据时,会检查 observers
数组是否存在,如果存在就会更新每一个 computation
。值得注意的是在更新 computation
过程中会解除双方依赖关系,然后在 readSignal
时重新建立起依赖关系。
我们此次分析的案例是一个很基础的案例,如果你感觉意犹未尽,还可以查看 更多案例,尝试自己调试下代码,观察其运行流程。
转载自:https://juejin.cn/post/7143176487988887566