深入浅出 solid.js 源码 (十八)—— 一些响应式工具函数

478 阅读3分钟

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

这一篇来看一些工具函数,这些函数与 solid 的响应式流程控制有关,有一些也在 solid 的源码内部被大量使用,这些函数被导出作为工具函数,在 solid 应用中有时会用到。

首先来看 untrack,这个函数之前的文章中提到过,它的效果是忽略依赖执行逻辑。由于 solid 默认是响应式自动追踪依赖的,会存在某些时候我们就不需要某个依赖,我们想跳过默认的依赖,此时就可以借助 untrack 的能力,把这部分逻辑放在 untrack 的回调函数中执行就会忽略依赖。untrack 的实现如下:

export function untrack<T>(fn: Accessor<T>): T {
  let result: T,
    listener = Listener;

  Listener = null;
  result = fn();
  Listener = listener;

  return result;
}

非常简单,因为 solid 的监听是被添加到 Listener 中的,因此这里临时把 Listener 清空就可以不带任何依赖了,执行完成后再还原 Listener,就不会影响其他逻辑的执行。

接下来看 batch,batch 的效果是批量更新,这个概念熟悉 react 就很容易理解,react 中一项很重要的内部优化就是 batch update,当你调用 setState 之后并不能直接获取到更新后的结果,需要等待下次刷新后才能拿到新值,setState 只是把更新推入了待更新队列,在每个更新周期进行更新。

在 solid 中也有 batch,添加到 batch 中的逻辑不会立刻触发更新,会等到当前作用域结束统一更新,batch 在 solid 的源码中也有很多使用,它本身的实现也很简单:

export function batch<T>(fn: Accessor<T>): T {
  if (Pending) return fn();
  let result;
  const q: SignalState<any>[] = (Pending = []);
  try {
    result = fn();
  } finally {
    Pending = null;
  }

  runUpdates(() => {
    for (let i = 0; i < q.length; i += 1) {
      const data = q[i];
      if (data.pending !== NOTPENDING) {
        const pending = data.pending;
        data.pending = NOTPENDING;
        writeSignal(data, pending);
      }
    }
  }, false);

  return result;
}

接下来一个是 on,on 在 solid 源码中也有使用,on 的作用是实现显式监听更新,举个例子:

createEffect(on(a, (v) => console.log(v, b())));

上面的逻辑和下面的是等价的:

createEffect(() => {
  const v = a();
  untrack(() => console.log(v, b()));
});

on 后面还可以通过设置 defer 来限制只在更改时才计算,源码实现如下:

export function on<S, Next extends Prev, Prev = Next>(
  deps: AccessorArray<S> | Accessor<S>,
  fn: OnEffectFunction<S, undefined | NoInfer<Prev>, Next>,
  options?: OnOptions
): EffectFunction<undefined | NoInfer<Next>> {
  const isArray = Array.isArray(deps);
  let prevInput: S;
  let defer = options && options.defer;
  return prevValue => {
    let input: S;
    if (isArray) {
      input = Array(deps.length) as unknown as S;
      for (let i = 0; i < deps.length; i++) (input as unknown as TODO[])[i] = deps[i]();
    } else input = deps();
    if (defer) {
      defer = false;
      return undefined;
    }
    const result = untrack(() => fn(input, prevInput, prevValue));
    prevInput = input;
    return result;
  };
}

这里就是利用 untrack,手动管理数据值的变化过程,可以用在想要手动处理更新逻辑的某些场景下。

最后再来看一个 observable 函数,这个函数在一个单独的 observable 文件中,它可以把 signal 转化为 Observable 对象。由于 signal 本身就是基于响应式设计的,因此它可以很方便地和响应式编程库一起使用,其中的代表就是 rxjs:

import { from } from "rxjs";

const [s, set] = createSignal(0);

const obsv$ = from(observable(s));

obsv$.subscribe((v) => console.log(v));

接入 rxjs 后可以更方便数据处理,对于 rxjs 用户很友好,这里的源码实现也很简单,感兴趣可以去阅读一下,在此不做展开了。