Svelte 5 不是 JavaScript
在过去几周里,我一直忙于处理将一个Web应用程序升级到Svelte 5所带来的后果。抛开对框架更新换代和迁移烦恼的抱怨,我在迁移过程中遇到了一些有趣的问题。到目前为止,我没有看到很多人报告过相同的问题,所以我觉得自己阐述这些问题可能会有所帮助。
我会尽量不在这篇帖子中抱怨太多,因为我很感激多年来享受的Svelte 3/4。但我想我不会再选择Svelte来开发任何新的项目了。我希望我在这里的一些反思对其他人也会有所帮助。
如果您对我在这里提到的问题的复现感兴趣,可以在以下链接找到。
对速度的需求
首先,让我简要地认可一下Svelte团队所努力的方向。看起来版本5的大部分重大变化都是围绕“深层响应性”(deep reactivity)构建的,这允许更细粒度的响应性,从而带来更好的性能。这当然很好,Svelte团队在性能与开发体验(DX)的协调方面一直表现出色。
在Svelte的早期版本中,实现这一目标的主要方式是通过Svelte编译器。涉及许多辅助技术来提高性能,但拥有一个框架编译步骤给了Svelte团队很大的灵活性,可以在幕后重新排列事物,而无需让开发者学习新概念。这就是Svelte最初如此独特的原因。
同时,这也导致了一个比以往更加晦涩难懂的系统框架,使得开发者调试更复杂的问题变得更加困难。更糟糕的是,编译器存在缺陷,导致了一些只能通过“盲猜”重构问题组件才能修复的错误。我个人至少遇到过五六次这样的情况,这也是我最终转向Svelte 5的原因。
尽管如此,我始终认为这是为了速度和生产力而可以接受的权衡。当然,有时我不得不删除我的项目,并将其迁移到一个新的仓库,但这个框架确实是一个使用起来的乐趣。
Svelte 5更是加大了这种权衡的力度——这是有意义的,因为这正是该框架与众不同的地方。这次的不同之处在于,抽象/性能的权衡并没有停留在编译器领域,而是以两种重要的方式侵入了运行时:
-
使用proxies来支持deep reactivit
-
隐式组件生命周期状态。
这两个改动不仅提升了性能,还让开发者的API看起来更加整洁。为什么不喜欢呢?
不幸的是,这两个特性都是抽象泄漏的典型例子,最终是开发变得更加复杂,而不是更简单。
Proxies 不是 Objects
使用proxies似乎让Svelte团队能够在不要求开发者做额外工作的前提下,从框架中榨取更多性能。
在React等框架中,通过多个组件层级传递状态而不引发不必要的重新渲染,是一项臭名昭著的困难任务。 Svelte 的编译器避免了与虚拟 DOM 比较解决方案相关的一些陷阱,但显然仍有足够的性能提升,足以证明引入proxies的合理性。
Svelte 团队似乎也 认为 他们的引入代表了开发者体验的改进:
我们……可以最大化兼顾效率和人体工学。
问题是:Svelte 5 看起来 更简单,但实际上引入了 更多 的抽象。
使用proxies来监控数组方法很有吸引力,因为它允许开发者忘记确保状态是响应性的所有古怪启发式方法,只需向数组中 push
即可。我无法计算我在 Svelte 4 中写了多少次 value = value
来触发响应性。 在Svelte 4中,开发者必须了解Svelte编译器的工作原理。编译器作为一个有缺陷的抽象,迫使用户知道赋值是用来表示响应性的方式。在Svelte 5中,开发者可以“忘记”编译器!
但实际上,他们不能。所有新抽象的引入实际上只是引入了更多复杂的启发式方法,开发者必须将它们记在心里,以便让编译器按照他们的意愿工作。
事实上,这就是为什么在使用Svelte多年后,我发现自己在越来越多地使用Svelte stores,而响应性声明则越来越少。原因在于Svelte stores就是JavaScript。在store上调用update
很简单,而且能够用$
来引用它们只是个额外的便利——无需记住,如果编译器出错,它就会提醒我。
proxies引入了与响应性声明类似的问题,那就是它们看起来像一件事,但在边缘上却表现得像另一件事。 当我开始使用 Svelte 5 时,一切运行得都很顺利——直到我尝试将proxies保存到 indexeddb(GitHub 上的 issue),那时我遇到了 DataCloneError
。更糟糕的是,没有通过 try/catch
结构化克隆来可靠地判断某个对象是否是 Proxy
,这是一个性能密集型操作。
这迫使开发者记住哪些是proxies,哪些不是,每次将proxies传递给一个不期望或不知道它们的上下文时,都要调用 $state.snapshot
。这抵消了他们最初给予我们的所有美好抽象。
组件不是函数
虚拟 DOM 在 2013 年之所以能够流行起来,是因为它能够将应用程序建模为一系列组合函数,每个函数接收数据并输出 HTML。Svelte 保留了这种范式,使用编译器来规避虚拟 DOM 的低效和生命周期方法的复杂性。
在 Svelte 5 中,组件生命周期又回来了,采用了 react-hooks 风格。 在React中,hooks是一种抽象,它允许开发者避免编写与组件生命周期方法相关的所有状态代码。现代React教程普遍推荐使用hooks,这些hooks依赖于框架在不可见的方式下同步状态与渲染树。
虽然这确实会导致代码更简洁,但也要求开发者谨慎行事,以避免破坏围绕hooks的假设。只需尝试在setTimeout
中访问状态,你就会明白我的意思。
Svelte 4有几个类似的陷阱——例如,与组件的DOM元素交互的异步代码必须跟踪组件是否已卸载。这和你在依赖生命周期方法的旧React组件中看到的那种模式非常相似。
在我看来,Svelte 5通过添加与组件生命周期相关的隐式状态来协调状态变化和效果,似乎是走上了React 16的道路。 例如,以下是 $effect 文档的摘录:
您可以将 $effect 放置在任何位置,而不仅仅是组件的最顶层,只要它在组件初始化期间(或父级效果激活时)被调用。然后它将与组件(或父级效果)的生命周期相关联,因此当组件卸载(或父级效果被销毁)时,它将自动销毁。
这非常复杂!为了有效地使用 $effect...(抱歉),开发者必须理解状态变化是如何被追踪的。组件生命周期 文档 声称:
在 Svelte 5 中,组件的生命周期只包含两个部分:其创建和其销毁。介于两者之间的一切——当某些状态更新时——与组件整体无关;只有需要响应状态变化的那些部分才会收到通知。这是因为底层最小的变化单位实际上不是组件,而是组件在初始化时设置的(渲染)效果。因此,并没有“更新前”/“更新后”钩子这样的东西。
然而,它接着介绍了与 $effect.pre
结合的“tick”概念。本节解释说,“tick
返回一个promise,它在任何挂起的州变化被应用后解决,或者在下一个微任务中如果没有挂起的变化时解决。”
我确信有一些心理模型可以证明这一点,但我不认为当必须紧接着关于状态变化的补充说明时,声称组件的生命周期仅由挂载/卸载组成真的很有帮助。 这个地方真正让我感到困扰,也是这篇博客帖子的动机所在,那就是当状态与组件的生命周期耦合在一起时,即使这个状态被传递给一个对Svelte一无所知的函数。
在我的应用程序中,我通过在存储中保存我想要渲染的组件及其属性来管理模态对话框,并在应用程序的layout.svelte
中渲染它。这个存储也与浏览器历史同步,以便使用后退按钮关闭它们。有时,向这些模态之一传递一个回调是有用的,将调用者特定的功能绑定到子组件上:
const {value} = $props() const callback = () => console.log(value) const openModal = () => pushModal(MyModal, {callback})
这是JavaScript中的一个基本模式。传递回调只是你做的事情之一。
不幸的是,如果上述代码位于模态对话框本身中,调用组件会在回调被调用之前被卸载。在Svelte 4中,这运行得很好,但在Svelte 5中,当组件卸载时,value
会被更新为undefined
。这里有一个最小化复制的例子。
这只是一个例子,但对我来说,很明显,任何被生命周期比其组件长的回调函数封闭的属性,在我想要使用它时都会是undefined
——没有任何重新赋值存在于词法作用域中。
这根本不是JavaScript的工作方式。我认为Svelte之所以这样做,是因为它试图重新发明垃圾回收。因为value
是组件的属性,它显然需要在组件生命周期的末尾被清理。我确信这背后有很好的工程原因,但这确实令人惊讶。
结论
简单的事情很美好,但正如Rich Hickey所说,简单的事情并不总是简单的。而且像Joel Spolsky一样,我不喜欢感到意外。Svelte一直充满了魔法,但在我看来,随着最新版本的发布,重复咒语的认知成本终于超过了它赋予的力量。
在这篇文章中,我的目的并不是贬低Svelte团队。我知道很多人喜欢Svelte 5(以及react hooks)。我试图表达的观点是,在为用户做事和赋予用户自主权之间有一个权衡。好的软件是建立在理解之上,而不是聪明之上。
我也认为,随着AI辅助编码越来越受欢迎,记住这一点非常重要。不要选择让你与工作疏远的工具。选择那些利用你已经积累的智慧,并帮助你深化对这门学科理解的工具。 感谢Rich Harris及其团队多年来愉快的开发经历。我希望(如果你看到这段话的话),其中的不准确之处不至于影响作为用户反馈的价值。

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
工人日报:越来越多科创企业选择开源影响几何
《工人日报》(2025年02月18日 07版)记者 杨冉冉 2025年伊始,中国本土科创企业的代表DeepSeek,成为一匹AI创新黑马,其推出的开源通用人工智能模型DeepSeek-V3和R1系列,以低成本、高性能震动全球科技界。 同样引发高度关注的还有宇树科技,16个身着花袄、手持彩绢的宇树H1人形机器人站上春晚舞台,以一场灵动欢快的“扭秧歌”表演惊艳世界。 值得关注的是,这两家科创企业得到全球科技巨头认可的背后,有一个关键核心要素——那就是都选择了开源。DeepSeek将R1训练技术全部公开,通过开源为全球开发者提供了一个创新与应用的开放平台。宇树科技则在2024年宣布开源强化学习代码库,吸引全球超万名开发者参与创新。 什么是开源?开源可以看作源代码可开放共享的开发模式,具有大众协同、开放共享、持续创新的特性。其源起于软件领域,并发展延伸到开源硬件、开源设计,还有由贡献者、用户和爱好者组成的开源社区。在智能化转型的浪潮下,开源在云计算、大数据、区块链、人工智能、生物工程、脑科学、智能驾驶、机器人、工业软件等新赛道不断深化。 公开数据显示,目前,全球97%的软件开发者和99%的企业...
- 下一篇
天天 AI-20250219
官宣!OpenAI前CTO新公司:北大校友翁荔加盟,创始29人2/3来自OpenAI 前OpenAI首席技术官Mira Murati宣布成立新公司——Thinking Machines Lab。该团队由29名成员组成,其中三分之二来自OpenAI,包括前研究副总裁Barret Zoph和联合创始人John Schulman。Mira Murati担任CEO,团队的目标是帮助人们调整AI系统以满足特定需求,开发强大的基础模型,并培养开放的科学文化。Thinking Machines Lab旨在构建一个人人都能获得知识和工具的未来,让AI能够为人类的独特需求服务。该团队强调科学共享的重要性,计划定期发布技术博客和论文,以促进研究文化的改善。来源原文 ChatGPT后训练方法被OpenAI离职联创公开,PPT全网转~ 离开OpenAI的John Schulman和Barret Zoph公开了ChatGPT后训练方法的PPT,分享了他们在斯坦福的演讲内容。后训练阶段是模型开发的最后一步,旨在让模型更像助手,确保其适合实际生产环境。PPT中详细介绍了后训练的三个主要组成部分:监督微调、奖励模...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- SpringBoot2整合MyBatis,连接MySql数据库做增删改查操作
- CentOS8安装Docker,最新的服务器搭配容器使用
- Jdk安装(Linux,MacOS,Windows),包含三大操作系统的最全安装
- SpringBoot2整合Redis,开启缓存,提高访问速度
- CentOS6,7,8上安装Nginx,支持https2.0的开启
- SpringBoot2编写第一个Controller,响应你的http请求并返回结果
- CentOS7编译安装Cmake3.16.3,解决mysql等软件编译问题
- CentOS7设置SWAP分区,小内存服务器的救世主
- Windows10,CentOS7,CentOS8安装MongoDB4.0.16
- SpringBoot2全家桶,快速入门学习开发网站教程