发布于 2026-01-05 2 阅读
0

Elm vs. Svelte:1. 代码大小;2. 性能;3. 架构;4. 声明式编程 vs. 命令式编程;5. 静态类型 vs. 动态类型;6. 数据绑定;7. 单一数据源;8. 编译器。更多实际应用案例和性能分析。那么,谁才是赢家呢?🏆

榆树 vs. 苗条

1. 代码大小

2. 表演

3. 建筑

4. 声明式编程与命令式编程

5. 静态类型与动态类型

6. 数据绑定

7. 单一真实来源

8. 编译器

关于实际应用案例和性能的更多细节

那么,谁是赢家呢?🏆

替代文字

我写这篇关于 Elm 和 Svelte 的(可能结论并不明确的)文章时感到很开心。Rich Harris 在《编写更少的代码》一书中展示了一小段 Svelte 代码,作为用少量代码完成某项任务的示例。

我用 Elm 写了同样的代码,并使用以下标准将其与 Svelte 进行了比较:

  1. 代码大小
  2. 表演
  3. 建筑
  4. 声明式编程与命令式编程
  5. 静态类型与动态类型
  6. 数据绑定
  7. 真实性的唯一来源
  8. 编译器

1. 代码大小

不出所料,Elm 的代码量更多。

苗条版

<script>
    let a = 1;
    let b = 2;
</script>
<input type="number" bind:value={a}>
<input type="number" bind:value={b}>
<p>{a} + {b} = {a + b}</p>
Enter fullscreen mode Exit fullscreen mode

工作演示

榆树版

module Main exposing (main)

import Browser
import Html exposing (..)
import Html.Attributes exposing (..)
import Html.Events exposing (..)

init = { a = "1", b = "2" }

type Msg = ChangeA String | ChangeB String

update msg model =
    case msg of
        ChangeA value -> { model | a = value }
        ChangeB value -> { model | b = value }

view model =
    div []
        [ input [ onInput ChangeA, value model.a, type_ "number"] []
        , input [ onInput ChangeB, value model.b, type_ "number"] []
        , p []
            [ text <| String.join " "
                [ model.a
                , "+"
                , model.b
                , "="
                , case Maybe.map2 (+) (String.toFloat model.a) (String.toFloat model.b) of
                    Just value -> String.fromFloat value
                    Nothing -> "undefined"
                ]
            ]
        ]

main = Browser.sandbox { init = init, view = view, update = update }
Enter fullscreen mode Exit fullscreen mode

工作演示

代码字符(不含空格):

Elm.....: 630 characters 
Svelte..: 127 characters
Enter fullscreen mode Exit fullscreen mode

(*) 我统计字符数时会移除所有制表符/空格,复制到剪贴板,然后运行 ​​pbpaste | wc -c

已压缩

Elm.....:  ~27 KB
Svelte..:   ~3 KB
Enter fullscreen mode Exit fullscreen mode

哇!这几乎大了一个数量级。

但是等等,如果 Elm 用 630 个字符的代码生成了 27 KB 的文件,我想它肯定添加了一些以后会很有用的额外功能。

让我们分析一下真实世界示例应用程序(ElmSvelte)的资源占用情况:

Elm.....:  ~29 KB (  +2 KB)
Svelte..:  ~15 KB ( +12 KB)
Enter fullscreen mode Exit fullscreen mode

是的,Elm 的增量构建时间比 Svelte 的增量构建时间短。这两个数字会反过来吗?我的意思是,是否存在 Svelte 的构建时间比 Elm 的构建时间长(不使用代码分割)的情况?这是一个很有意思的问题,但我目前还没有答案。

如果你想看更多精彩的“少代码”示例,可以查看官方列表。我特别喜欢待办事项应用中的动画效果

2. 表演

除非你创建包含复杂动画、视频游戏或显示大量数据的页面,否则在现代硬件/浏览器中性能都不是问题。

但就上述情况而言,Svelte 和 Elm 的性能不相上下(参见此处此处此处)。Svelte 直接与 DOM 交互,而 Elm 则使用经过优化的虚拟 DOM,充分利用了其纯粹性。关于这两种方法的有趣讨论,请参见此处此处

理论上,直接修改 DOM 的完美脚本性能最佳。基于虚拟 DOM 的系统也需要做到这一点,此外,它还需要管理虚拟 DOM。

实际上,要编写一个能够适用于多种情况的完美脚本是不可能的,所以 Elm 和 Svelte 的表现难分伯仲

这是对原生 JavaScript、Svelte、Imba、Elm、Vue、React 和 Angular 的性能比较。代码越环保越好。

替代文字

3. 建筑

Elm 内置了Elm 架构。这有点过度设计,但对于这个小型应用程序来说,使用 Elm(或者任何其他框架)本身就有点过度设计了。Elm 解决方案只是一个框架,可以随时扩展。

使用 Elm,您将拥抱一种声明式的纯函数式语言,它具有不可变性、模式匹配、类型推断、静态类型等特性。它既有优点也有缺点。

如果你不熟悉纯函数的概念,它们是指“输入相同,输出相同”且没有副作用的函数。

所有 Elm 函数都是这样的。这正是 Elm 可靠且易于调试的原因之一。

我猜 Svelte 可以用类似 Elm 架构的东西来编写(Svelte 有“on:input”吗?)。

4. 声明式编程与命令式编程

对比这两个代码片段,Elm 版本似乎有更多样板代码,Elm 更侧重于“如何做”,而 Svelte 版本则更侧重于“做什么”。

我完全赞成陈述式方法(“什么”而不是“如何”),但我们需要在两者之间保持良好的平衡,否则就会变得像魔法一样。

在这个小例子中,双向数据绑定隐藏了一些类型强制转换,这可能会产生意想不到的行为。

5. 静态类型与动态类型

处理数字类型的输入字段相当棘手。我之前使用 Vue 和 React 时就遇到过不少问题。相比之下,Svelte 在处理这类字段时返回“undefined”方面做得非常好。为了更好地理解这个问题,你可以尝试在数字类型的输入字段中输入“ee”或其他任何非数字字符。浏览器会返回一个空字符串。Svelte 使用了一些开发者无法直接看到的机制来处理这个问题。

我想,神奇之处在于编译器生成的这个函数:

function to_number(value) {
    return value === '' ? undefined : +value;
}
Enter fullscreen mode Exit fullscreen mode

在 Elm 示例中,同样的问题也在将结果打印到屏幕之前通过以下这行代码得到了解决:

Maybe.map2 (+) (String.toFloat model.a) (String.toFloat model.b)
Enter fullscreen mode Exit fullscreen mode

对于不熟悉 Elm 的人来说,这行代码的作用大致如下:

“我需要计算两个浮点数的数学和,这两个浮点数以字符串形式存储(HTML 输入字段的自然输出也是字符串,即使它们被设置为字符串类型number)。所以首先我需要将这两个字符串转换为数字,但转换可能会失败。如果其中任何一个转换失败,我也希望求和运算失败。”

那行代码的结果是Elm 中表示可能出错的情况。该类型的两个可能值Maybe Float耶!一切顺利,这是你漂亮的浮点数)或(糟糕,出了点问题,抱歉,没有给你数字)。MaybeMaybe FloatJust FloatNothing

如果没有输入数字,我们会将其打印undefined到屏幕上,这只是为了模仿 Svelte 的示例,因为实际上undefinedElm 中并不存在这种null做法。

JavaScript 小技巧

是的,是“bites”,而不是“bytes”,就像 Javascript 类型系统咬你一样。

仍然与类型有关,如果您将 a 和 b 的类型更改为字符串,如下所示:

<script>
    let a = "1";
    let b = "2";
</script>
<input type="number" bind:value={a}>
<input type="number" bind:value={b}>
<p>{a} + {b} = {a + b}</p>
Enter fullscreen mode Exit fullscreen mode

工作演示

浏览器会将其渲染为:“1 + 2 = 12”,因为 JavaScript 中的“+”运算符可以与任何类型(包括字符串)连接(它会将字符串连接起来)。Svelte 在后台进行了一些类型转换,但在这个例子中,该函数to_number在初始化期间没有运行。

在严格类型的语言中,情况并非如此。如果你将a`or`初始化b为字符串,编译器会报错,因为“+”只接受数字

一旦Svelte 支持 TypeScript,这些问题或许就能得到解决

类型灵活性

补充说明一下,虽然 Svelte 版本将a`and`定义b为类型number,但在 Elm 中,我们将它们定义为字符串:

init = { a = "1", b = "2" }
Enter fullscreen mode Exit fullscreen mode

我决定使用字符串,因为这是 HTML 自然输出的数据类型。然后在添加之前,我再将它们转换为浮点数。

如果我们想将它们存储为数字,我们宁愿使用

init = { a = Just 1, b = Just 2 }
Enter fullscreen mode Exit fullscreen mode

考虑到字符串到数字转换过程中可能出现的故障。

6. 数据绑定

Elm 没有自动双向数据绑定功能。从这个意义上讲,Elm 更像是原始的 HTML。Elm 代码如下:

input [ onInput ChangeA, value model.a, type_ "number" ] []
Enter fullscreen mode Exit fullscreen mode

这是 HTML 中的类似物

<input oninput="...", value=model.a, type="number">
Enter fullscreen mode Exit fullscreen mode

绑定是通过 ` onInputand` 和 `value` 属性完成的,其中 `and`是调用带有消息和模型的函数"..."的东西,伪代码如下: updateChangeAupdate( [ "ChangeA", this.value ], model )

在 Svelte 中:

<input type="number" bind:value={a}>
Enter fullscreen mode Exit fullscreen mode

绑定采用原始的bind:条款。

两种方法各有优缺点。Elm 方法需要更多配置,但允许你在必要时修改流程。Svelte 方法更简单,并且对流程隐藏得很深。

7. 单一真实来源

在 Elm 中,根据设计,应用程序生命周期内只能修改一个“对象”(即记录model)。在本例中,我们选择了一个包含值 a 和 b 的记录。

在 Svelte 示例中,有两个值被修改,它们是组件级别的状态。Svelte 中有几种维护状态的方法:`state`、`state` 和 `state`。`state` 是将状态保存在Stores组件Context外部Props的方法,可以是 `state` `state` 和` Storesstate` 类型writablereadablederivedcustom

同样,在 Elm 中,状态仅在应用层存在。其他任何层级都不能拥有独立的状态。

Elm 中的所有事件都会被转换为消息。在这个简单的示例中,我们使用了两条消息:

type Msg = ChangeA String | ChangeB String
Enter fullscreen mode Exit fullscreen mode

一条消息用于更新输入字段 a,另一条消息用于更新输入字段 b。我们本可以使用一条消息同时更新这两个字段:

type Msg = Change InputField String
Enter fullscreen mode Exit fullscreen mode

自定义类型在哪里InputField?或者更通用一点(但这并非 Elm 的最佳实践):

type Msg = Change String String
Enter fullscreen mode Exit fullscreen mode

这并非一种好的做法,因为在 Elm 中,你希望编译器尽可能严格,以便在编译时而不是执行时拦截错误。如果使用了 String 类型,例如,当你传递一个既不是 `String` 也不是 `String` 的字符串时,编译器就无法a报错b

8. 编译器

Elm 和 Svelte 都有编译器,而且都能编译成 Javascript。

Elm 编译器由 26K 行 Haskell 代码组成,而Svelte 编译器由 15K 行 TypeScript 代码组成。

Elm 编译器会生成一个大型的 Javascript 文件,该文件已与 Elm 运行时打包在一起,可直接使用。它有 3 种模式:普通模式、调试模式(启用时间旅行调试器)和优化模式(启用优化以使代码更小、更快)。

Svelte 编译器会生成一个小型 JavaScript 文件,该文件随后会与Svelte 运行时打包在一起。您可以在这里这里找到对编译后文件的分析

Svelte 编译器有几种模式,其中最重要的有:服务器端渲染、开发、CSS(将 CSS 包含在 JavaScript 中并在运行时注入)、Hydratable、Immutable(告诉编译器您承诺不会改变任何对象)、Legacy(可在 IE9 和 IE10 中运行)。

关于实际应用案例和性能的更多细节

RealWord 示例可能已过时或实现不佳,因此请谨慎看待这些观察结果。

我用一些实际示例做了简单的测试,但没有发现任何显著差异。在网络连接速度较慢的情况下,Svelte 版本在性能指标上表现更快(它还使用了Elm 所没有的代码分割),但从视觉效果来看,Elm 版本的渲染速度更快。Svelte 版本会长时间显示“正在加载…”的文本,这可能是由于某些实现问题造成的。在 Svelte 版本中,浏览器分 4 个时间段下载 7 个 JavaScript 代码块,页面的主要部分只有在第四个时间段下载完毕后才会开始加载。

所有测试均在配备“慢速 3G”网络的 MacBook Pro 上使用 Chrome 79.0.3945.88 进行。

左边是榆树,右边是苗条。性能相似:

左边是榆树,右边是苗条。性能相似。

Svelte——资源被放置在四个槽位中,总共七个数据块。当第四个数据块加载时,页面仍然显示“正在加载……”。这是实现问题吗?

Svelte——资产以四个插槽的形式提供。

苗条——最后一块

苗条——最后一块

榆树——第一块(也是最后一块)

榆树——第一块(也是最后一块)

那么,谁是赢家呢?🏆

我们只是浅尝辄止地了解了这两种技术,但我们可以宣布哪一种技术是赢家吗?

是的,获胜者就是为合适的任务选择合适工具的人。

这篇文章着重指出了这两个框架之间可能存在的主要权衡取舍之一。

Svelte 让你与 HTML/CSS/Javascript 保持紧密联系,Elm 和 elm-ui 则允许你脱离它们,以换取一些好处,例如,没有运行时异常

其他概念,如学习曲线、逐步采用、性能、占用空间大小等,都还有待商榷。

我赞赏 Svelte 为前端工程领域带来的诸多创新理念,我也会继续尝试使用它。互相借鉴是好事,我们应该互相学习(或者说是互相借鉴?)。

与此同时,我将继续使用 Elm,因为我相信它最适合我正在开发的应用程序。我也是 elm-ui 的忠实用户,再去编写 CSS 实在没什么吸引力。

纯函数式编程,加上严格的类型和类型推断,其整个概念感觉像是一种更高层次的编程形式,这让我很有共鸣。

(本文最初发表于Medium

文章来源:https://dev.to/lucamug/elm-vs-svelte-7k4