createEffect 源码分析

https://www.solidjs.com/docs/latest/api#createeffect

createEffect 会创建一个 computation ,当在函数体内执行 getter 函数,会自动收集相关依赖。当依赖更新时,会重新运行该函数。

下面我们将使用这段代码对源码进行分析:

const [count, setCount] = createSignal(0);

createEffect(() => {
  console.log("count =", count());
});

setCount(count() + 1);
typescript

依赖收集

createEffect

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;
typescript

可以看到 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);
}
typescript

首先将 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)
typescript

第 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;
}
typescript

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);
}
typescript

首次执行时,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;
}
typescript

首先判断 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;
}
typescript

在 cleanNode 中,首次使用 createEffect,大部分逻辑都不会走到, 这时 computation 对象的 sources、owned 都为空,不过这时会触发 (node as Computation<any>).state = 0;, 将原本 computation 对象的 state 设置为 0,原本为 1。

然后定义 ownerlistener 缓存已经存在的 OwnerListener ,这时的 ownerlistener 都为空。

接下来的操作比较关键,将 node 赋值给 ListenerOwner ,这时如果调用 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;
}
typescript

此时 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;
  }
}
typescript

首先定义 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());
});
typescript

我们在函数中使用了名为 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;
}
typescript

这段代码其实我们之前已经分析过,它会返回当前 state 的 value 值,即我们定义的属性值。

不过当时 Listener 并不存在,现在情况则不同,当我们调用 updateComputation 方法时,Listener 已经被赋值为 node ,即 computation 对象。

Listener = Owner = node;
typescript

目前的 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;
}
typescript

这里的 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);
typescript

首先调用 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];
}
typescript

由于这里的 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;
}
typescript

此时 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;
typescript

由于此时 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);
  }
}
typescript

判断 Updates 是否存在,如果存在,直接返回函数执行。

定义 wait 变量,赋值 false,!init 为真,将 Updates 赋值为空数组。此时 Effects 并不存在,赋值为空数组,ExecCount 变量自增,此时为 1。

然后执行函数内部逻辑,将 computation 添加到 Effects 数组中。

const res = fn();
completeUpdates(wait);
typescript

最后调用 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();
}
typescript

首先判断 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;
}
typescript

然后判断 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();
typescript

将 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);
}
typescript

这里的 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]);
}
typescript

首先循环遍历 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
}
typescript

可以看到,如果是用户 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!);
 	// ...
}
typescript

跳过上述判断逻辑后,开始正式执行更新逻辑。

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;
    }
  }
}
typescript

首先定义数组 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;
  }
}
typescript

由于 runnintTransition 不存在,并且 node.state 为 1 ,等于 STALE,所以会执行这行代码。

updateComputation(node);
typescript

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;
}
typescript

这里的执行逻辑和我们之前分析依赖收集基本一致。

不同点在于这里的 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;
}
typescript

这里的 computation 是存在 sources 数组的,所以会对 sources 数组及其依赖的 observers 数组进行清理,解除两者的依赖关系。然后再将 computationstate 设置为 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;
  }
}
typescript

定义变量 nextValue,执行 node.fn,将返回值赋值给 nextValue。这里的 node.fn 就是我们的副作用函数:

createEffect(() => {
  console.log("count =", count());
});
typescript

执行副作用函数会重新进行取值操作,收集依赖,需要注意的是这里 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;
}
typescript

此时控制台会打印出:

count = 1
typescript

因为此时 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;
typescript

然后会回到 updateComputation,将 Listener 和 Owner 重新赋值为缓存的 listener 和 owner。

继续回退,直至清空函数调用栈,代码执行完毕。

总结

我们已经完成对 solid.js 的依赖收集和更新过程分析。这里对执行过程做一下总结。

首先是依赖收集过程,当我们使用 createEffect 时:

  • 首先会将 runUserEffects 赋值给 runEffects
  • 然后会调用 createComputation 创建 computation 对象
  • 标识 computation 为用户 computation
  • 接着会调用 updateComputation 方法
    • 清洗 node(computation) ,设置 computation 对象的 state 为 0
    • 缓存上次的 OwnerListener,设置 time 属性值,将 node(computation) , 赋值给 ListenerOwner
    • 调用 runComputation 方法
      • 定义 nextValue 属性
      • 执行用户传入的副作用函数,此时会执行 getter,获取最新值,赋值给 nextValue
        • 因为此时 Listener(computation) 已经存在,所以会和 siganlState 建立起依赖关系;
        • 只有这样我们才可以在设置响应式数据时,获取依赖当前数据的观察者数据,进行更新。
      • 因为此时 Transitionnode.updatedAt 都不存在,所以会把最新值赋值给 computation.value ,这里也是 undefined
      • 设置 nodeupdatedAt 属性
    • 还原 ListernerOwner

然后是更新流程,当我们使用 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 数组,赋值给变量 eEffects 重新设置为 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 对象添加到 singleStateobservers 数组中
  • singleState 对象添加到 computationsources 数组中

当我们设置响应式数据时,会检查 observers 数组是否存在,如果存在就会更新每一个 computation 。值得注意的是在更新 computation 过程中会解除双方依赖关系,然后在 readSignal 时重新建立起依赖关系。

我们此次分析的案例是一个很基础的案例,如果你感觉意犹未尽,还可以查看 更多案例,尝试自己调试下代码,观察其运行流程。