深入浅出 solid.js 源码 (十五)—— 流程控制

·  阅读 52

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

这一节来看流程控制部分。在 react 中是不存在这部分内容的,因为 react 允许 jsx 自由发挥,流程控制完全由 js 本身来控制,想要写循环可以直接使用 map,想要写条件判断可以直接使用 if 语句。这样处理的好处是没有引入额外概念,jsx 本身也只是 js 的语法糖,直接写 js 逻辑也很自然,处理起来没有专门的 API 限制。但是这样也会存在一些不足,由于语法自由大家的编码风格各异,因此很难在编译阶段做优化。这里可以对比一下 vue3 和 react18,由于 vue 使用模板描述 UI,想要添加条件只能使用 v-if、想写循环只能 v-for,这样在编译阶段就可以提前预判模块内容,比如某段计算结果是确定的,直接可以编译静态值进来。这样可以在编译阶段做更多的事情,相比较而言 react 中条件可以使用 if 可能是 switch也可能是 &&,而且这些都是混在逻辑之中的,没有一个边界且没有一个明确的规则,想要在编译阶段优化发挥很有限。事实也是如此,由于有静态编译的加持,vue3 单从编译结果来看就优于 react。

solid 致力于在编译阶段做更多的事情,因此在这一部分的设计上还是更多考虑了性能因素,因此 solid 在 jsx 的基础上增加了一些限制,使用这些限制来扩大编译时优化的影响,在尽可能保留 jsx 优势的前提下提升编译优化效率。这些限制简单来说就是规范了条件和循环的写法,通过提供内置组件的方式代替自由的写法。

Show 组件是 solid 中用来处理条件语句的组件,用法大概如下:

<Show when={state.count > 0} fallback={<div>Loading...</div>}>
  <div>My Content</div>
</Show>
<Show when={state.user} fallback={<div>Loading...</div>}>
  {(user) => <div>{user.firstName}</div>}
</Show>
复制代码

这部分的组件实现位于 render 目录下 flow.ts 中:

export function Show<T>(props: {
  when: T | undefined | null | false;
  fallback?: JSX.Element;
  children: JSX.Element | ((item: NonNullable<T>) => JSX.Element);
}) {
  let strictEqual = false;
  const condition = createMemo<T | undefined | null | boolean>(() => props.when, undefined, {
    equals: (a, b) => (strictEqual ? a === b : !a === !b)
  });
  return createMemo(() => {
    const c = condition();
    if (c) {
      const child = props.children;
      return (strictEqual = typeof child === "function" && child.length > 0)
        ? untrack(() => (child as any)(c as T))
        : child;
    }
    return props.fallback;
  }) as () => JSX.Element;
}
复制代码

这部分的代码逻辑很好理解,就是利用 memo 监听条件的变化,根据不同的条件决定是返回子元素内容还是 fallback 内容。

当条件语句比较复杂时,还可以使用 Switch 和 Match 来代替 Show,用法如下:

<Switch fallback={<div>Not Found</div>}>
  <Match when={state.route === "home"}>
    <Home />
  </Match>
  <Match when={state.route === "settings"}>
    <Settings />
  </Match>
</Switch>
复制代码

这个就没什么好说的了,对应了 switch 语句的效果,这部分实现起来很简单,原理和 Show 类似,只是改为处理多组行为,感兴趣可以自行阅读。

For 和 Index 是两个用来处理循环的组件,这两个组件的区别可以对比一下:

<For each={state.list} fallback={<div>Loading...</div>}>
  {(item, index) => (
    <div>
      #{index()} {item}
    </div>
  )}
</For>
<Index each={state.list} fallback={<div>Loading...</div>}>
  {(item, index) => (
    <div>
      #{index} {item()}
    </div>
  )}
</Index>
复制代码

Index 擅长处理固定索引修改内容的场景,For 擅长处理的是变化索引的场景,他们的源码中,for 调用的是 mapArray,index 调用的是 indexArray,这两个函数都位于 reactive 下面的 array.ts 文件中,里面涉及到数组的渲染处理,如何更新,如何根据索引或内容进行优化,逻辑本身分支比较多因此看起来比较长,其实原理比较容易理解,感兴趣可以自行阅读源码。对于更多的开发者而言需要关注 For 和 Index 的应用场景,选择正确的组件可以提升循环执行效率。

收藏成功!
已添加到「」, 点击更改