深入浅出 solid.js 源码 (十七)—— 其它响应式 API

313 阅读2分钟

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

除了前面提到的基础 API,solid 还提供了一些不太常用的响应式 API,这些 API 大部分时候用不到,不过有些特殊的场景需要使用,这一节来看一下这部分 API,目前有 createDeferred、createComputed、createRenderEffect 和 createSelector 这几个。

首先是 createDeferred,它创建了一个只在浏览器空闲时才会触发的更新。我们前面看到的 signal 值更新后会立刻触发响应,添加了 deferred 效果之后相当于把这个响应延后,我们来看实现逻辑:

export function createDeferred<T>(source: Accessor<T>, options?: DeferredOptions<T>) {
  let t: Task,
    timeout = options ? options.timeoutMs : undefined;
  const node = createComputation(
    () => {
      if (!t || !t.fn)
        t = requestCallback(
          () => setDeferred(() => node.value as T),
          timeout !== undefined ? { timeout } : undefined
        );
      return source();
    },
    undefined,
    true
  );
  const [deferred, setDeferred] = createSignal(node.value as T, options);
  updateComputation(node);
  setDeferred(() => node.value as T);
  return deferred;
}

可以看到其内部创建了一个新的 signal,这个 signal 的更新时间会延后,这个延后的调度策略是在 requestCallback 中实现的。这里阅读调度器的逻辑可以看到最终是通过 MessageChannel 来实现的,这里会创建一个宏任务,因此会在浏览器空闲时间更新。关于调度器的详细逻辑后面会深入分析,本文暂时不展开。

接下来是 createComputed,效果是创建一个计算,可以在渲染前执行,对比 memo 它的执行优先级更高,createComputed 常用在实现一些底层 API,在 solid 的源码内部会看到很多处使用。它的源码很简单:

export function createComputed<Next, Init>(
  fn: EffectFunction<Init | Next, Next>,
  value?: Init,
  options?: EffectOptions
): void {
  const c = createComputation(fn, value!, true, STALE, "_SOLID_DEV_" ? options : undefined);
  if (Scheduler && Transition && Transition.running) Updates!.push(c);
  else updateComputation(c);
}

接下来是 createRenderEffect,在 react 中有 useEffect 和 useLayoutEffect,这里也比较类似,和 createEffect 相比,createRenderEffect 的执行时机靠前,前者是在渲染后执行,后者是在渲染阶段执行,源码和 createComputed 几乎一样,区别只在是否产生副作用的处理上:

export function createRenderEffect<Next, Init>(
  fn: EffectFunction<Init | Next, Next>,
  value?: Init,
  options?: EffectOptions
): void {
  const c = createComputation(fn, value!, false, STALE, "_SOLID_DEV_" ? options : undefined);
  if (Scheduler && Transition && Transition.running) Updates!.push(c);
  else updateComputation(c);
}

最后一个是 createSelector,这是一个用于优化性能的函数,举个例子:

const isSelected = createSelector(selectedId);

<For each={list()}>
  {(item) => <li classList={{ active: isSelected(item.id) }}>{item.name}</li>}
</For>;

这里只有在匹配时才会触发更新,用于类似的列表场景可以提升处理性能,在其底层是添加了对 key 的判断,对于命中 key 做特殊处理,可以提升列表的处理性能。这部分的源码就不贴了,重点还是关注这种优化方式,在列表中这类场景还是比较常见的。

本文提到的几个函数通常使用的不多,因此不需要重点了解,至于什么时候使用,如果你不知道是否需要使用那就不用。