Svelte 使用心得:在个人项目中表现不错,但在大型企业项目中仍有待观察

作者:Ty Hopp
  • 2023-08-30
    北京
  • 本文字数:2744 字

    阅读完需:约 9 分钟

作者用了 1 个月时间开发了一款个人 RSS 阅读器,并选择了 Svelte 和 SvelteKit 作为 Web 客户端的工具。这个选择的主要目的是为了评估这些工具是否适合在大型项目中使用。作者发文分享了对于 Svelte 的一些思考,这篇文章引起了 Hacker News 上读者的关注,并且被顶到了首页。fenomas 和 illilarian 这两位用户谈到了他们对于过渡和动画 API 的看法。如果需要在 Svelte 管理的元素进入和离开 DOM 时对其进行动画处理,那么作者“吐槽”的这些 API 就非常有用。看来作者之前的抱怨不成立了。如果是你,你会把 Svelte 用到大型公开项目中吗?

 

以下是这篇“吐槽”原文,由 InfoQ 翻译。

 


 

过去一个月来,我开发了一款个人 RSS 阅读器。

 

在 Web 客户端这边,我选的是 Svelte 和 SvelteKit,主要是为了评估这些工具适不适合在大型项目里使用。

 

下面跟大家聊聊我自己对于 Svelte 的一点思考。

 

开篇总结

 

总的来说,我挺喜欢 Svelte 的使用体验。它的亮点在组件格式、内置 store 和事件调度程度 API。

短板主要是响应式语句($)、await 块和内置的过渡与动画 API。

 

组件格式

 

Svelte 的组件格式最得我心。在编写.svelte 文件时,默认上下文跟浏览器是完全相同的,都是用 HTML。

 

以下片段来自 Svelte 文档(包括示例标记、JS 和 CSS),应该可以说明问题:

 

<script>  // logic goes here  function add(a, b) {    return a + b;  }</script><!-- markup (zero or more items) goes here --><p>1 + 2 is: {add(1, 2)}</p><style>  /* styles go here */  p {    color: blue;  }</style>
复制代码

再来跟 React 对比一下,这里的默认上下文是 JavaScript,而 HTML 要通过 JSX 进行交错:

import '../some-styles.css'; // styles are imported into JS filesexport function SomeComponent() {  // logic can go anywhere  function add(a, b) {    return a + b;  }  // markup is returned from JS functions  return <p className="some-class">{`1 + 2 is: ${add(1, 2)}`}</p>;}
复制代码

我没法斩钉截铁地告诉大家,这种方式就一定更好,比如“Svelte 的组件格式能让团队在构建组件时,比某某框架快多少倍。”这个我确实不敢说。

 

但我觉得组件格式确实是很多朋友喜爱 Svelte 的原因。这可能是因为浏览器也优先使用 HTML,所以用 Svelte 的话上下文切换较少,但我不确定是不是这样。总之,我个人非常喜欢。

 

内置 store

 

Svelte 为状态管理提供内置的 store 选项。

 

其实大家对于用户界面库/框架应该关注什么、没必要关注还有争议。而 Svelte 聪明的地方,就在于它承认状态管理可能会成为问题,而且提供了相应的解决方案。大家可以根据需要使用或者扩展。

 

更贴心的是,这个解决方案不像 React 上下文那样跟组件树紧密相关。

事件调度程序 API

 

Svelte 提供一个内置 API 可用于创建、分派和在父元素上侦听 CustomEvent。

 

在基于单向数据流概念构建的系统中,其实很难为 Web 事件建模。从本质上讲,Web 的事件模型会让数据向上流动。

 

Svelte 承认用户可能需要向树结构的上方发送数据,并提供一个使用 Web 平台原语的 API。我必须给它点个赞!

响应式语句

我发现 Svelte 的响应式语句有点让人摸不着头脑。

 

Svelte 的基本响应基于变量分配。通过文档中的以下示例,我已经弄明白了:

 

<script>  let count = 0;  function handleClick() {    // calling this function will trigger an    // update if the markup references `count`    count = count + 1;  }</script>
复制代码

 

这个很合理。

 

但接下来在引入响应式 $标签时,文档给出了以下示例:

 

<script>  export let title;  export let person;  // this will update `document.title` whenever  // the `title` prop changes  $: document.title = title;  $: {    console.log(`multiple statements can be combined`);    console.log(`the current title is ${title}`);  }  // this will update `name` when 'person' changes  $: ({ name } = person);  // don't do this. it will run before the previous line  let name2 = name;</script>
复制代码

考虑到这是文档里关于该主题的第一个示例,似乎显得太复杂了。但只要认真看下来,还是能理解其中逻辑的。

 

但 Svelte 文档又提到:

请务必注意,响应块在统计时会通过简单的静态分析进行排序,所有编译器查看的都是分配给块本身、并在块内部使用的变量,而不在它们调用的任何函数当中。这意味着以下示例中,yDependent 不会随着 x 的更新而一同更新:

<script>  let x = 0;  let y = 0;  const setY = (value) => {    y = value;  };  $: yDependent = y;  $: setY(x);</script>
复制代码

 

这种“玄学”般的设计,让我在很多情况下都想不明白为什么组件无法更新。

 

最终我发现,确实很难明确认定 $ 标签是否起效。有时候我用起来一切正常,但有时候用起来就没有效果,非常诡异。

 

所以我决定离它远点。另一个类似的问题是访问 store 值,它跟 $ 的情况差不多,时灵时不灵。

 

正是 $ 标签阻止了我在大型项目中使用 Svelte。这是 Svelte 的核心部分,不可能彻底回避,而且我觉得由此引发错误的可能性很高、而且影响范围很大。

Await 块

 

Svelte 提供{#if ...} 和 {#each ...} 语法作为标记渲染的主要控制流方法。它还提供{#await ...},可以根据 Promise 的状态来决定渲染什么。

 

我喜欢这个设计思路,但在实践中总是以重构告终。在 Promise 被解决或拒绝之后,我总得再调整一下才能开始渲染,所以我可不打算每次运行服务时都用它。

 

而且该逻辑也不属于渲染代码中的内联。那它到底是怎么工作的?

 

把{#await ...}剔出来并放进