深入浅出 solid.js 源码 (十二)—— 生命周期

317 阅读3分钟

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

前面已经看过了 createSignal、createEffect 和 createMemo 三个最常用的的响应式 API,solid 中其他响应式 API 也都是在这些底层能力的基础之上构建的,由于这些 API 相对不常用我们放到后面再看,本篇文章我们来看 solid.js 中的生命周期。

在真实的应用场景中,我们需要在挂载完成之后做一些事情,比如请求页面资源或一些初始化操作。在 react 中,我们 useEffect 的依赖是手动添加的,我们可以通过把依赖设置为空的方式实现类似能力。但是在 solid.js 中,依赖是自动收集的,只要有依赖默认就必须要监听,这时我们需要一种能忽略指定依赖的方式,在 solid 中提供了 untrack API 来处理这样的场景。

untrack 接收一个函数作为参数,效果是忽略块中的依赖执行函数中的代码块。当你需要某一部分不监听依赖时,可以使用 untrack 手动设置解除监听。untrack 的实现很简单:

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

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

  return result;
}

就是在执行 untrack 逻辑时临时的把 Listener 清空,这样就不会有任何监听触发。借助 untrack 我们可以 createEffect 不监听依赖,只在挂载完成时执行一次:

createEffect(() => untrack({
	// ...
}));

非常简单,但是我们不需要每次都这样手动写,solid 已经帮我们实现了这个函数:

export function onMount(fn: () => void) {
  createEffect(() => untrack(fn));
}

onMount 是 solid 的一个生命周期,我们可以直接使用这个函数为组件添加初始化逻辑。

有了初始化就需要有销毁,与 onMount 对应的就需要有一个 onCleanup 生命周期。它可以在组件销毁时执行,可以添加清理动作。在 solid 中,onCleanup 不止可以添加在组件中,还可以添加到 effect 中,在 effect 完成后执行清理:

createEffect(() => {
	console.log("CreateEffect");
	onCleanup(() => {
    console.log("CreateEffect Cleanup");
	});
});

onCleanup 的实现也很简单:

export function onCleanup(fn: () => void) {
  if (Owner === null)
    "_SOLID_DEV_" &&
      console.warn("cleanups created outside a `createRoot` or `render` will never be run");
  else if (Owner.cleanups === null) Owner.cleanups = [fn];
  else Owner.cleanups.push(fn);
  return fn;
}

清理的函数会被添加到当前调用 Owner 的 cleanups 数组中,在每次执行 cleanNode 时都会遍历 cleanups 并执行。

solid 中另一个生命周期是 onError,它可以捕获当前模块的错误触发回调,源码实现也比较少:

export function onError(fn: (err: any) => void): void {
  ERROR || (ERROR = Symbol("error"));
  if (Owner === null)
    "_SOLID_DEV_" &&
      console.warn("error handlers created outside a `createRoot` or `render` will never be run");
  else if (Owner.context === null) Owner.context = { [ERROR]: [fn] };
  else if (!Owner.context[ERROR]) Owner.context[ERROR] = [fn];
  else Owner.context[ERROR].push(fn);
}

调用 onError 就是把错误回调添加到 Owner 的 context 下面,实际触发调用是在 handleError 回调中,这个回调会在每次 runUpdates 和 runComputation 的 catch 中触发,因此每一个 update 或 computation 的异常都会被最近的一个 onError 捕获到。