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