正文
这两个改动不仅提升了性能,还让开发者的 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
。这抵消了他们最初给予我们的所有美好抽象。
组件不是函数