好得很程序员自学网

<tfoot draggable='sEl'></tfoot>

基于JavaScript介绍性能爆表的SolidJS

前言

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

背景

前段时间,产品提了个临时需求,让我给开发一个独立部署的首页可视化页面,因为目前我们系统提供以 iframe 的方式实现用户自己选择 URL 配置一个页面,作为应用的其中一个路由页面使用~

想着就单独部署一个页面,也没必要使用 React 或者 Vue 了,简单画个页面完事, jq 我是用不动了

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

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

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

对于我这种小项目还是比较适合的~

介绍

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

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

官方给出的理由:

高性能  - 始终在公认的  UI  速度和内存利用率基准测试中名列前茅 强大  - 可组合的反应式原语与  JSX  的灵活性相结合 务实  - 合理且量身定制的  API  使开发变得有趣而简单 生产力  - 人体工程学和熟悉程度使构建简单或复杂的东西变得轻而易举

主要优势

高性能 - 接近原生的性能,在  js-framework-benchmark  排名中名列前茅 极小的打包体积 - 编译为直接的 DOM操作 ,无 虚拟DOM ,极小的运行时(类似于  Svelte ),适合打为独立的  webComponent  在其它应用中嵌入 易于使用 - 近似  React  的使用体验,便于快速上手

对比分析

我们把关注点聚焦于是否使用虚拟DOM,以及数据的响应处理。

虚拟DOM的分析

首先,虚拟DOM并不是一定比原生性能好,或者说是更快,抛开真实场景不谈都是瞎扯淡,框架的设计和应用场景是有它自身考量的。

在状态与Dom操作之间抽象出一层虚拟Dom,需要牺牲一定的运行时性能,并不一定比直接操作原生Dom快,要看情况,毕竟 diff 并不是免费的。

不管你的数据变化多少,每次重绘的性能都是可以接受(提供过的去的性能)。 你依然可以用类似  innerHTML  的思路去写你的应用。 最最重要的一点,实现了跨平台。

如 react ,对于web端的渲染可以使用 react-dom ,对于native的渲染可以使用 react-native 、以及服务端渲染等,他们的开发模式非常类似,按照 react 的语法规则进行即可,但是在 render 层,只要符合 react api 规范,你可以提供各种不同的 render 渲染函数,进行跨平台的渲染实现。

核心原理的选择

拿我们熟悉的 react 和 vue 说明:

React对数据的处理是不可变( immutable ):具体表现是整树更新,更新时,不关注是具体哪个状态变化了,只要有状态改变,直接整树diff找出差异进行对应更新。 Vue对数据的处理是响应式、可变的( mutable ):更新时,能够精确知道是哪些状态发生了改变,能够实现精确到节点级别的更新(类似的框架还有Svelte、SolidJS)。

更新粒度的选择

应用级 :有状态改变,就更新整个应用,生成新的虚拟Dom树,与旧树进行 Diff (代表作:React,当然了,现在它的虚拟Dom已升级为了 Fiber )。 组件级 :与上方类似,只不过粒度小了一个等级(代表作: vuev2 及之后的版本)。 节点级 :状态更新直接与具体的更新节点的操作绑定(代表作 vue1.x 、 Svelte 、 SolidJS )。

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

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

是否采用虚拟DOM

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

是 :对应用级的这种更新粒度,虚拟Dom简直是必需品,因为在 diff 前它并不能得到此次更新的具体节点信息,必须要通过随后的 虚拟Dom+Diff算法 筛选出最小差异,不然整树 append 对性能是灾难(代表框架: React 、 vue )。 但这里值得注意的事是:本质上 vue 并不需要 虚拟DOM ,因为它这种基于依赖收集的响应式机制可以直接进行节点级更新,但 vue 借助 虚拟DOM 的抽象能力,可以做到更新粒度的随意调整(目前是组件级),给vue的发展提供更多可能性, 尤其在跨平台渲染方面,这点十分关键。 否 :对节点级更新粒度的框架来说,一般没有必要采用虚拟dom(代表作: vue1.x 、 Svelte 、 SolidJS )。

开发语法DSL选择

JSX : React 、 SolidJS 模版+编译指令 : 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 变得简洁许多。

调用栈分析:

简单分析:

组件函数只会在整个应用生命周期里调用一次。 心智模型与 react 完全不一样,反而与 vue3 保持了一致,可以说兼具了 React hooks  +  vue3 的优点 createEffect 自动追踪依赖,不需要像 react 那样维护一个 dep 数组 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 DOM  的  diff/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诞生以来,整个前端的开发范式都发生了翻天覆地的变化,各种类似框架也是层出不穷,他们各有各的优势。

对我们开发者来说,对于同一类型框架熟练掌握一种足矣,大可不必每种框架都学习一遍,我们需要做到对其内部实现原理的知悉,做到知其然也知其所以然,正所谓一法通万法皆通,当我们打牢基础之后再去使用和学习其他框架便轻而易举了,并在实践中拓展知识广度和深度。

对于不同类型框架,了解其优势以及一些独有的特殊思路和实现,做到心中有数,也有益于我们的技术成长。

这样我们在之后的实际开发过程中便可结合具体场景做到更合适的技术选型~

到此这篇关于基于JavaScript介绍性能爆表的SolidJS的文章就介绍到这了,更多相关JS SolidJS内容请搜索以前的文章或继续浏览下面的相关文章希望大家以后多多支持!

查看更多关于基于JavaScript介绍性能爆表的SolidJS的详细内容...

  阅读:39次