“我报名参加金石方案1期挑战——分割10万奖池,这是我的第1篇文章,点击查看活动详情”

运用预编译、无虚拟DOM、究极融合怪、功用爆表、React的异父异母亲兄弟——SolidJs

功用爆表的SolidJS
功用爆表的SolidJS

布景

前段时刻,产品提了个暂时需求,让我给开发一个独立布置的主页可视化页面,由于现在咱们系统供给以iframe的方法完成用户自己挑选URL装备一个页面,作为运用的其中一个路由页面运用~

想着就单独布置一个页面,也没必要运用React或许Vue了,简略画个页面完事,jq我是用不动了

想着最近出社区也出现了不少风趣的结构,之前看svelte,就觉得挺有意思的,感觉也比较契合我的运用场景,正准备用这个上手搞一波呢

然后,在GitHub发现了solidjs这个项目,大致看了下,好家伙,这简直比reactreact~

看了下文档,写了demo试了下,很容易上手,又看了一些比照测验和博客介绍,感觉功用很强啊,和svelte一样都是预编译,没有运行时,构建产物十几kb,与原生js相差无几,令人惊叹~

关于我这种小项目仍是比较适合的~

不多逼逼,直接上手干~

介绍

官方介绍:用于构建用户界面的声明式、高效且灵活的 JavaScript

Solid 运用了相似 Svelte 的预编译,语法运用上相似于 React,运用 JSX 语法和非常相像的API,但不同于 React,组件只会初始化一次,并不是 state 改动就重新运行烘托整个组件,这相似于 Vue3setup和呼应式更新(更新颗粒度为节点级)

官方给出的理由:

  • 高功用 – 一直在公认的 UI 速度和内存利用率基准测验中独占鳌头
  • 强大 – 可组合的反应式原语与 JSX 的灵活性相结合
  • 务实 – 合理且量身定制的 API 使开发变得风趣而简略
  • 生产力 – 人体工程学和熟悉程度使构建简略或复杂的东西变得垂手可得

首要优势

  • 高功用 – 挨近原生的功用,在 js-framework-benchmark 排名中独占鳌头
  • 极小的打包体积 – 编译为直接的DOM操作,无虚拟DOM,极小的运行时(相似于 Svelte),适合打为独立的 webComponent 在其它运用中嵌入
  • 易于运用 – 近似 React 的运用体会,便于快速上手

比照剖析

咱们把重视点聚集于是否运用虚拟DOM,以及数据的呼应处理。

虚拟DOM的剖析

首要,虚拟DOM并不是一定比原生功用好,或许说是更快,抛开实在场景不谈都是瞎扯淡,结构的设计和运用场景是有它本身考量的。

在状况与Dom操作之间笼统出一层虚拟Dom,需求献身一定的运行时功用,并不一定比直接操作原生Dom快,要看状况,毕竟diff并不是免费的。

  1. 不论你的数据变化多少,每次重绘的功用都是能够接受(供给过的去的功用)。
  2. 你依然能够用相似 innerHTML 的思路去写你的运用。
  3. 最最重要的一点,完成了跨平台。

react,关于web端的烘托能够运用react-dom,关于native的烘托能够运用react-native、以及服务端烘托等,他们的开发形式非常相似,依照react的语法规则进行即可,但是在render层,只需契合react api标准,你能够供给各种不同的render烘托函数,进行跨平台的烘托完成。

核心原理的挑选

拿咱们熟悉的reactvue阐明:

  • React对数据的处理是不可变(immutable):详细表现是整树更新,更新时,不重视是详细哪个状况变化了,只需有状况改动,直接整树diff找出差异进行对应更新。
  • Vue对数据的处理是呼应式、可变的(mutable):更新时,能够准确知道是哪些状况发生了改动,能够完成准确到节点级别的更新(相似的结构还有Svelte、SolidJS)。

更新粒度的挑选

  • 运用级:有状况改动,就更新整个运用,生成新的虚拟Dom树,与旧树进行Diff(代表作:React,当然了,现在它的虚拟Dom已升级为了Fiber)。
  • 组件级:与上方相似,只不过粒度小了一个等级(代表作:vuev2及之后的版本)。
  • 节点级:状况更新直接与详细的更新节点的操作绑定(代表作vue1.xSvelteSolidJS)。

vue1.x时代,关于数据是每个生成一个对应的Wather,更新颗粒度为节点级别,但这样创建大量的Wather会形成极大的功用开销,因此在vue2.x时代,经过引入虚拟DOM优化呼应,做到了组件级颗粒度的更新。

而关于react来说,虚拟DOM便是至关重要的部分,甚至是核心,咱们已经了解react是属于运用级别的更新,因此整个DOM树的更新开销是极大的,所以这儿关于虚拟DOM+diff算法的运用便是极其必要的。包含现在的fiber架构与可中断更新,也算是对虚拟DOM的极致压榨。

是否选用虚拟DOM

这个挑选是与上边选用何种粒度的更新设计严密相关的:

  • :对运用级的这种更新粒度,虚拟Dom简直是必需品,由于在diff前它并不能得到此次更新的详细节点信息,必需求经过随后的虚拟Dom+Diff算法筛选出最小差异,否则整树append对功用是灾难(代表结构:Reactvue)。
    • 但这儿值得注意的事是:本质上vue并不需求虚拟DOM,由于它这种根据依靠搜集的呼应式机制能够直接进行节点级更新,但vue借助虚拟DOM的笼统才能,能够做到更新粒度的随意调整(现在是组件级),给vue的发展供给更多可能性, 尤其在跨平台烘托方面,这点非常要害。
  • :对节点级更新粒度的结构来说,一般没有必要选用虚拟dom(代表作:vue1.xSvelteSolidJS)。

开发语法DSL挑选

  • JSXReactSolidJS
  • 模版+编译指令vue(JSX可选)、Svelte

正文

根本运用

import { render } from 'solid-js/web';
import { createSignal, createEffect } from 'solid-js';
const CountingComponent = () => {
  const [count, setCount] = createSignal(0);
  createEffect(() => console.log('count', count()));
  const handleAdd = () => {
    setCount((prev) => prev + 1);
  };
  return <div onClick={handleAdd}>Count value is {count()}</div>;
};
render(() => <CountingComponent />, document.getElementById('app'));

这简直是React hooks的双胞胎兄弟…

并且由于SolidJS这种后发优势,没有React沉重的历史包袱,比方不需求处理类组件的兼容(SolidJS只支撑函数式)这让它在完成了大部分React功用特性的前提下,源码体积要比React小许多,这让它在首屏加载方面就首要占有上风。直接调用编译好的DOM操作方法,省去了虚拟DOM比较这一步所耗费的时刻,整个更新链路比较React变得简练许多。

调用栈剖析:

功用爆表的SolidJS

功用爆表的SolidJS

简略剖析:

  1. 组件函数只会在整个运用生命周期里调用一次。
  2. 心智模型与react完全不一样,反而与vue3保持了一致,能够说兼具了React hooks + vue3的优点
  3. createEffect自动追踪依靠,不需求像react那样维护一个dep数组
  4. hook调用次序没要求,以函数调用的方法处理Proxy目标有必要是对象的问题

它的呼应式完成确实是与vue一样,都是根据发布订阅的依靠搜集去做的,但它没有选用vue虚拟Dom的运行时diff,而是充沛在编译阶段做文章,将状况更新编译为独立的DOM操作方法。

编译内容剖析

import { render, createComponent, delegateEvents, insert, template } from 'solid-js/web';
import { createSignal, createEffect } from 'solid-js';
const _tmpl$ = /*#__PURE__*/template(`<div>Count value is </div>`, 2);
const CountingComponent = () => {
  const [count, setCount] = createSignal(0);
  createEffect(() => console.log('count', count()));
  const handleAdd = () => {
    setCount(prev => prev + 1);
  };
  return (() => {
    const _el$ = _tmpl$.cloneNode(true);
          _el$.firstChild;
    _el$.$$click = handleAdd;
    insert(_el$, count, null);
    return _el$;
  })();
};
render(() => createComponent(CountingComponent, {}), document.getElementById('app'));
delegateEvents(["click"]);

能够看到,跟根据 Virtual DOM 的结构比较,这样的输出不需求 Virtual DOMdiff/patch 操作,自然能够省去大量的运行时代码。而是运用了solid-js/web库供给的insert等DOM函数操作。

再结合以后的webcomponent考虑下,真是大有可为,发展空间很大,未来可期~

项目实战

直接依照官方文档示例,创建一个支撑TypeScript的基础项目,模板默认运用vite构建(solidjs-templates)

# Typescript template
$ npx degit solidjs/templates/ts my-solid-project
$ cd my-solid-project
$ npm install # or pnpm install or yarn install

在vite中引入插件

import solidPlugin from "vite-plugin-solid"
export default defineConfig({
  plugins: [solidPlugin()],
})

入口文件装备如下:

import { render } from "solid-js/web"
import App from "./App"
render(() => <App />, document.getElementById("root"))

接下来就能够开始写业务代码了,便是这么简略~

import { Title, List, Chart } from "./components"
import { onMount, onCleanup, createSignal } from "solid-js"
import request from "./utils/request"
import type { Component } from "solid-js"
import type { DataProps, ValueType } from "./typings"
import cls from "./index.module.less"
const URL = "/statistic/hrm"
const App: Component = () => {
  const [getValue, setValue] = createSignal<ValueType>(null)
  // mount
  onMount(() => {
    request<DataProps>({ method: "GET", url: URL }).then((res) => {
      if (res) {
        console.log("res", res)
        setValue(res)
      }
    })
  })
  // unmount
  onCleanup(() => {
    // ...
  })
  return (
    <div class={cls.App}>
      <Title />
      <List list={getValue()?.list} />
      {getValue()?.pieData && <Chart data={getValue()?.pieData} />}
    </div>
  )
}
export default App

List组件

import type { Component } from "solid-js"
import { ListProps } from "../typings"
import cls from "../index.module.less"
// List
const List: Component<{ list: ListProps[] }> = (props) => {
  return (
    <ul class={cls.list}>
      {props.list?.map(({ label, value }) => (
        <li>
          <div class={cls.label}>{label}</div>
          <div class={cls.value}>{value}</div>
        </li>
      ))}
    </ul>
  )
}
export default List

Echart可视化组件

import { onMount, onCleanup } from "solid-js"
import type { Component } from "solid-js"
import echarts, { ECOptionPie } from "../../utils/echart"
import { OptionProps } from "../../typings"
// Chart
const Chart: Component<{ data: OptionProps[] }> = (props) => {
  let container: null | HTMLDivElement = null
  let instance
  // 性别散布
  const Option: ECOptionPie = {
    // data: props.data || [],
    // ...
  }
  onMount(() => {
    instance = echarts.init(container)
    instance.setOption(Option)
    window.addEventListener("resize", () => instance?.resize())
  })
  onCleanup(() => {
    window.removeEventListener("resize", () => instance?.resize())
  })
  return <div ref={container}></div>
}
export default Chart

以上是我根据项目简化的demo,怎么样,看起来是不是和react特别像,运用起来也是相当简略了~

总结

自react和虚拟DOM诞生以来,整个前端的开发范式都发生了天翻地覆的变化,各种相似结构也是层出不穷,他们各有各的优势。

对咱们开发者来说,关于同一类型结构熟练掌握一种足矣,大可不必每种结构都学习一遍,咱们需求做到对其内部完成原理的知悉,做到知其然也知其所以然,正所谓一法通万法皆通,当咱们打牢基础之后再去运用和学习其他结构便垂手可得了,并在实践中拓宽常识广度和深度。

关于不同类型结构,了解其优势以及一些独有的特殊思路和完成,做到心中有数,也有益于咱们的技能成长。

这样咱们在之后的实践开发过程中便可结合详细场景做到更合适的技能选型~

参阅

  • solid
  • SolidJS硬气的说:我比React还react
  • Solid-js 基础教程
  • 你听说过 No DomDiff 吗?