• home > webfront > engineer > Architecture >

    新星SolidJS/Svelte/Lit对比

    Author:zhoulujun Date:

    Vue 和 React 拥有广泛的社区支持和丰富的生态系统,是受众最广的前端生态Svelte 和 SolidJS 因其编译时优化和直接DOM操作而性能遥遥领先。 SolidJS 叫板 React 的核心理念:面相状态驱动而不是面向视图驱动。

    jquery时代更新视图是直接对DOM进行操作,缺点是频繁操作真实 DOM,性能差。

    angularJS把我们带入了MVVM时代,react和vue时代引入了虚拟DOM——更新视图是对新旧虚拟DOM树进行一层层的遍历比较,然后找出需要更新的DOM节点进行更新。

    这样做的缺点就是如果DOM树很复杂,在进行新旧DOM树比较的时候性能就比较差了。

    那么有没有一种方法是不需要去遍历新旧DOM树就可以知道哪些DOM需要更新呢?

    在编译时我们就能够知道哪些节点是静态的,哪些是动态的。在更新视图时只需要对这些动态的节点进行靶向更新,就可以省去对比新旧虚拟DOM带来的开销。vue3也是这样做的,甚至都可以抛弃虚拟DOM。但是考虑到渲染函数的灵活性和需要兼容vue2,vue3最终还是保留了虚拟DOM。 

    具体阅读:《vue3早已具备抛弃虚拟DOM的能力了

    Svelte 和 SolidJS 因其编译时优化直接DOM操作而通常表现出出色的性能,而越来越受欢迎

    Svelte声称,虚拟DOM是纯粹的开销

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

    SolidJS与React一样,也是使用JSX编写代码,但是与React不同的是,SolidJS没有使用React中使用的VDOM技术,而是直接处理DOM树。这种处理方式就赋予了SolidJS接近原生Javascript的性能。

    但并不意味着,虚拟DOM老兵已死

    1. 不管你的数据变化多少,每次重绘的性能都是可以接受(提供过的去的性能)。

    2. 你依然可以用类似 innerHTML 的思路去写你的应用。

    3. 最最重要的一点,实现了跨平台。

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

    Vue 和 React 拥有广泛的社区支持和丰富的生态系统,目前还是受众最广的前端生态


    chatGPT总结的表格

    特性Vue.jsReactSolidJSSvelteLit
    框架类型完整框架编译型框架
    虚拟DOM使用使用不使用不使用不使用
    响应式系统

    基于观察:

    Object.defineProperty(Vue 2.x)

    Proxy(Vue 3.x)

    不内置

    可用 hooks 实现

    基于 Proxy,细粒度更新机

    使用createSignal等API创建响应式状态

    基于观察者和编译

    编译时确定响应式依赖

    基于属性变更监听

    使用@state和@property装饰器定义

    模板语法HTML-likeJSX类似于 JSXHTML-likeHTML template literals
    状态管理Vuex

    Redux

     Context API, etc.

    Reactive stateReactive stateReactive properties
    编译不需要不需要不需要需要不需要
    性能优化

    异步批处理更

    依赖追踪和懒加载新

    组件级别的缓存

    Diff算法优化

    React.memo

    React.useMemo

    直接DOM操作编译时优化,移除框架代码

    部分更新

    高效的模板编

    适用于快速渲染新

    学习曲线较低中等较低较低较低
    生态支持丰富非常丰富成熟但较新成熟但较新专门针对Web Components
    可测试性良好良好良好良好非常好
    可组合性组件化组件化组件化组件化Web Components
    bundle size
    ReactDOM 大约120 KBSolidJS大约是18KB

    Svelte约为2KB

    但生成的代码大小不一

    Lit大约是16KB


    SolidJS 遵循 React 的理念,但使用了不同的技术。

    Svelte 对 UI 使用compile-time方法。

    对数据的处理

    拿熟悉的react和vue说明:

    • React对数据的处理是不可变(immutable):具体表现是整树更新,更新时,不关注是具体哪个状态变化了,只要有状态改变,直接整树diff找出差异进行对应更新。

    • Vue对数据的处理是响应式、可变的(mutable):更新时,能够精确知道是哪些状态发生了改变,能够实现精确到节点级别的更新(类似的框架还有Svelte、SolidJS)。

    Reactivity是一种声明性的方式来表达数据改变的传播。

    当我们有一种声明性地表达数据绑定的方式时,我们需要一种有效的方式来让框架传播数据改变。

    • React引擎将渲染的结果与之前的结果进行比较,并将差异应用于DOM本身。这种处理变化传播的方式被称为虚拟 DOM。

    • 在SolidJS中,这是以其存储和内置元素更明确地完成的。例如,"Show"元素将跟踪内部的变化,而不是虚拟 DOM。

    • 在Svelte中,"反应式 "代码被生成。Svelte知道哪些事件会导致变化,它会生成直接的代码,在事件和DOM变化之间划清界限。

    • 在Lit中,反应性是通过元素属性完成的,基本上是依靠HTML自定义元素的内置反应性。


    调试

    当我们使用或调试网络应用时,我们看到的代码与我们写的完全不同。我们现在依靠不同质量的特殊调试工具来反向设计网站上发生的事情,并将其与我们自己代码中的错误联系起来。

    • 在React中,调用栈从来不是 "你的"--React为你处理调度。当没有bug的时候,这很好用。但如果你想找出无限循环重现的原因,你就会陷入痛苦的境地。

    • 在Svelte中,库本身的体积很小,但你要运送和调试一大堆神秘的生成代码,这些代码是Svelte对反应性的实现,根据你的应用需求定制。

    • 对于Lit来说,它不太需要构建,但为了有效地调试它,你必须了解它的模板引擎。这可能是我对框架持怀疑态度的最大原因。

    当你寻找自定义的声明式解决方案时,你最终会面临更痛苦的命令式调试。



    这个对比太笼统,还不如单独做react 与SolidJs、Svelte 选型对比……

    SolidJs vs react

    React的异父异母亲兄弟(总体来说,像是一个mobx without magic + react without naiveness),但SolidJs性能遥遥领先!

    前端框架性能对比前端框架性能测试对比数据


    小甜甜SolidJs 

    • 高性能 - 接近原生的性能,在 js-framework-benchmark 排名中名列前茅

    • 极小的打包体积 - 编译为直接的DOM操作,无虚拟DOM,极小的运行时(类似于 Svelte),适合打为独立的 webComponent 在其它应用中嵌入

    • 易于使用 - 近似 React 的使用体验,便于快速上手


    因为SolidJS直接采用hooks大法(函数式)这种后发优势,没有React沉重的历史包袱,比如不需要处理类组件的兼容(SolidJS只支持函数式)这让它在实现了大部分React功能特性的前提下,源码体积要比React小很多,这让它在首屏加载方面就首先占据上风。直接调用编译好的DOM操作方法,省去了虚拟DOM比较这一步所消耗的时间,整个更新链路相比React变得简洁许多。

    SolidJS全面拥抱函数式

    仅支持 FunctionComponent

    SolidJS 仅支持 FunctionComponent 写法,无论内容是否拥有状态管理,也无论该组件是否接受来自父组件的 Props 透传,都仅触发一次渲染函数。

    所以其状态更新机制与 React 存在根本的不同:

    • React 状态变化后,通过重新执行 Render 函数体响应状态的变化

    • Solid 状态变化后,通过重新执行用到该状态代码块响应状态的变化

    与 React 整个渲染函数重新执行相对比,Solid 状态响应粒度非常细,甚至一段 JSX 内调用多个变量,都不会重新执行整段 JSX 逻辑,而是仅更新变量部分:

    const App = ({ var1, var2 }) => (
      <>
        var1: {console.log("var1", var1)}
        var2: {console.log("var2", var2)}
      </>
    );

    上面这段代码在 var1 单独变化时,仅打印 var1,而不会打印 var2,在 React 里是不可能做到的。

    这一切都源于了 SolidJS 叫板 React 的核心理念:面相状态驱动而不是面向视图驱动。正因为这个差异,导致了渲染函数仅执行一次,也顺便衍生出变量更新粒度如此之细的结果,同时也是其高性能的基础,同时也解决了 React Hooks 不够直观的顽疾,一箭 N 雕。

    更完善的 Hooks 实现

    SolidJS 用 createSignal 实现类似 React useState 的能力,虽然看上去长得差不多,但实现原理与使用时的心智却完全不一样:

    const App = () => {
      const [count, setCount] = createSignal(0);
      return <button onClick={() => setCount(count() + 1)}>{count()}</button>;
    };

    我们要完全以 SolidJS 心智理解这段代码,而不是 React 心智理解它,虽然它长得太像 Hooks 了。一个显著的不同是,将状态代码提到外层也完全能 Work:

    const [count, setCount] = createSignal(0);
    const App = () => {
      return <button onClick={() => setCount(count() + 1)}>{count()}</button>;
    };

    这是最快理解 SolidJS 理念的方式,即 SolidJS 根本没有理 React 那套概念,SolidJS 理解的数据驱动是纯粹的数据驱动视图,无论数据在哪定义,视图在哪,都可以建立绑定

    这个设计自然也不依赖渲染函数执行多次,同时因为使用了依赖收集,也不需要手动申明 deps 数组,也完全可以将 createSignal 写在条件分支之后,因为不存在执行顺序的概念。


    虽然 SolidJS 很棒,但相关组件生态还没有起来,巨大的迁移成本是它难以快速替换到生产环境的最大问题。前端生态想要无缝升级,看来第一步是想好 “代码范式”,以及代码范式间如何转换,确定了范式后再由社区竞争完成实现,就不会遇到生态难以迁移的问题了。

    另一方面,我还没有遇到任何专门招聘 Solid.js 开发人员的公司 !React 仍然是世界上最流行和使用最广泛的框架,无论您在哪里生活或工作,无论您身在何处,您都会遇到公司、个人、政府机构甚至咖啡店想要雇用React 开发人员。使用 React 有无数的工作机会,但请记住,伴随着无数的工作机会,也有无数人希望接受这些工作。



    参考文章:

    精读《SolidJS》 https://juejin.cn/post/7137100589208436743?from=search-suggest



    转载本站文章《新星SolidJS/Svelte/Lit对比》,
    请注明出处:https://www.zhoulujun.cn/html/webfront/engineer/Architecture/9171.html