likes
comments
collection

深入浅出 solid.js 源码 (五)—— 从 render 开始

作者站长头像
站长
· 阅读数 58

这一篇开始正式进入源码中,和 react 很相似,solid.js 逐渐最终也是需要挂载在一个 DOM 节点上,我们调用的是 render 方法:

render(<App>, document.getElementById('app'))

我们就从 render 来入手,开始 solid 的学习。

先来看一下 render 方法是什么,solid 源码中大量使用了 export * from xxx 的写法,这样的好处是方便导入,但是对于源码阅读的角度来看就不够友好了。现在可以确定的是 render 位于 web 目录下,我们可以尝试在 web 目录中搜索 render 方法,不过很遗憾,搜不到。在 web 目录中有限的内容里,唯一有可能导出 render 的就是这一行:

export * from "dom-expressions/src/client";

这里导出的是 dom-expressions 中的内容,这是一个独立的 npm 包,我们可以去查看它的主仓库。

dom-expressions 严格来说也是 solid.js 的一部分,他也是由 solid 作者开发,内部封装了 dom 相关的转化逻辑,它和 solid 的关系类似于 react 和 react-dom,不过只是类似,实现原理上是不同的。react 是使用虚拟 dom 实现的渲染,react-dom 是实现了浏览器上的虚拟 dom 渲染能力。而 solid 完全是通过编译的手段完成的工作,因此并没有虚拟 dom 层,它是直接把原始逻辑编译成 dom 操作,而 dom-expressions 库提供的就是与此相关的能力:babel-plugin-jsx-dom-expressions 是一个 babel 编译插件,他实现了把 solid 逻辑转化为 dom 操作方法的功能,而这些 dom 的操作具体的实现,就是由 dom-expressions 来实现的,这样就通过编译的手段实现了 solid 组件的渲染能力。

下面以我们现在的 render 为例来看下它是如何实现的。首先我们去 dom-expressions 中搜索 render:

export function render(code, element, init) {
  let disposer;
  root(dispose => {
    disposer = dispose;
    element === document
      ? code()
      : insert(element, code(), element.firstChild ? null : undefined, init);
  });
  return () => {
    disposer();
    element.textContent = "";
  };
}

这里的 root 引自 rxcore,rxcore 并不是一个真实的包名,在 readme 文件中有介绍,它只是用来占位,需要使用 babel-plugin-transform-rename-import 插件来将其替换为实际路径。我们可以查看 solid 库的 babel 配置:

plugins: [
        [
          "babel-plugin-transform-rename-import",
          {
            replacements: [
              {
                original: "rxcore",
                replacement: "../../../packages/solid/web/src/core"
              },
              {
                original: "^solid-js$",
                replacement: "../../src",
              }
            ]
          }
        ],

可以看到在这里的 rxcore 实际上就是 web 下的 core,其中的 root 就是引自 solid-js 的 createRoot 方法,它位于 reactive 目录中 signal 文件下。

这里可以看到,我们的传入的组件即 code 参数是一个可调用的函数,并且在这里会被调用到,接下来我们来看一下组件是如何被处理和转化的。