深入浅出 solid.js 源码 (十一)—— 计算缓存

471 阅读2分钟

这是我参与「掘金日新计划 · 8 月更文挑战」的第11天,点击查看活动详情

这一篇来看 createMemo,createMemo 也是一个基础的响应式 API,它的效果也很容易理解,缓存计算结果,举个例子:

const getValue = createMemo(() => a() + b());
getValue();

这里的 createMemo 会收集 a 和 b 两个依赖计算一个新的值,这样后面如果需要相同计算时,可以直接获取 memo 值,不需要在每一个地方调用 a() + b(),这里的计算开销越大,优化效果越明显。

与 createEffect 类似,createMemo 也可以获取上一次的计算结果,这里同样支持第二个参数作为初始值:

const sum = createMemo((prev) => input() + prev, 0);

这就是 createMemo 的主要用法,memo 起到了对 signal 的传递作用,在多个依赖同一组计算的场景下,memo 可以把重新计算的次数由多次降为一次,我们来看一下这部分的源码实现。

export function createMemo<Next extends Prev, Init, Prev>(
  fn: EffectFunction<Init | Prev, Next>,
  value?: Init,
  options?: MemoOptions<Next>
): Accessor<Next> {
  options = options ? Object.assign({}, signalOptions, options) : signalOptions;

  const c: Partial<Memo<Init, Next>> = createComputation(
    fn,
    value!,
    true,
    0,
    "_SOLID_DEV_" ? options : undefined
  ) as Partial<Memo<Init, Next>>;

  c.pending = NOTPENDING;
  c.observers = null;
  c.observerSlots = null;
  c.comparator = options.equals || undefined;
  if (Scheduler && Transition && Transition.running) {
    c.tState = STALE;
    Updates!.push(c as Memo<Init, Next>);
  } else updateComputation(c as Memo<Init, Next>);
  return readSignal.bind(c as Memo<Init, Next>);
}

可以看到,createMemo 返回的也是一个 readSignal 的调用,这里传入的参数还是一个 Computation。这部分逻辑前面在 createEffect 中见过,这里也是一样的效果,Computation 的作用就是收集依赖在变更时触发计算通知。memo 和 effect 的作用是不同的,这里体现在计算时机和结果返回上。

memo 创建的 Computation 最终会被 Updates 队列收集,之后在 completeUpdates 阶段完成调用。执行 update 之后并没有结束,我们需要的不是执行过程,而是执行结果,这里的执行结果计算又是需要响应式通知的,于是这里就借助了 signal 的能力:在 memo 的 Computation 执行阶段,这里会调用 writeSignal,把计算后得到的新值以 signal 的形式写入,这样当前的 node 就可以触发和 signal 一样的更新通知逻辑,在调用位置我们只需要和 signal 一样正常收集依赖就可以了,因此最后 createMemo 返回的就是一个 readSignal 的调用。