因此,Svelte v5,可能是现有最好的前端框架的最新版本已经发布,它与之前的版本有很大不同。主要区别在于其核心:如何实现变量的反应性。由于这些变化,Svelte 变得更容易,同时也变得更困难。
由于自 v5@next.155 以来我一直在现实世界的微前端项目中与 Svelte v5 密切合作,我决定撰写本系列文章来传递我所获得的知识来帮助您理解、接受并有可能将您的代码迁移到 Svelte v5。
Svelte v4 的反应系统简直就是一件艺术品:Svelte 静态分析组件中的代码,然后生成在常规 JavaScript 变量发生变化时强制更改 DOM 的代码。简单、优雅且性能非常好。一个简单的例子:
<script lang="ts"> let clickCount = 0; function countClicks() { ++clickCount; } </script> <svelte:document on:click={() => countClicks()} /> <pre class="brush:php;toolbar:false">Clicks inside document: {clickCount}
这个简单的组件向文档对象添加了一个“click”事件监听器并计算点击次数。只需上面的代码就可以实时显示点击次数。太棒了,对吧?
就像生活中的一切一样,这并不完美。 Rich Harris(Svelte 的创建者)已经解释了这些注意事项,我不会在本文中详细介绍这些内容。我只提一个:代码重构。
更重要的警告之一是无法将该反应系统带到组件之外。例如,我们无法创建一个可重用的模块来封装示例中 countClicks 函数的实现。
由于反应性取决于静态分析,因此将函数移走并放入模块中会将变量突变隐藏到静态代码分析器中,然后反应性就会丢失。
符文一词指的是“魔法符号”,Svelte 采用这个术语来命名以下功能外观“魔法”术语:
$州
$道具
$可绑定
$衍生
$效果
Svelte v5 中的反应性由这些符文的使用控制。
值得注意的是,虽然它们看起来像 R 值,但它们生成的代码实际上是 L 值。换句话说,不要认为你可以在变量之间传递状态。下面的 $state rune 部分对此进行了详细介绍。
这种新反应系统的主要优点是:
能够在组件之外重构反应式代码
细粒度反应性
前者意味着我们可以在组件之外拥有响应式变量;后者意味着组件重新渲染在对状态变化做出反应时更有针对性。
本文未介绍在组件外部拥有状态的能力,但请关注本系列,因为将会有一篇关于此的文章。
另一方面,细粒度反应性意味着 Svelte 现在可以知道状态对象中的哪个属性发生了变化,并且仅重新渲染(并重新运行效果并重新计算派生值)受该特定属性影响的内容仅有的。在某些情况下,这是一个重大的性能改进。举个简单的例子:如果一个大型表格组件看到一个新行添加到其数据中,Svelte 将只渲染该新行。如果第三行中的单个单元格值发生更改,则只有显示该值的单元格才会重新渲染。现在清楚了吗?希望是这样,但如果不是,请在评论部分打我。
这个符文用于创建反应状态。让我们重写上面的 Svelte v4 代码示例:
<script lang="ts"> let clickCount = 0; function countClicks() { ++clickCount; } </script> <svelte:document on:click={() => countClicks()} /> <pre class="brush:php;toolbar:false">Clicks inside document: {clickCount}
为了达到与 Svelte v4 相同的结果,我们只需使用 $state(0) 而不是 0。
控制这个符文的主要规则是它只能用于初始化变量或类字段,这与您刚才读到的重要注释有关:符文在语法上看起来像函数,但事实并非如此。编译器用与函数功能(计算并返回值)的想法不兼容的代码替换符文。这意味着以下不会创建第二个反应变量:
<script lang="ts"> let clickCount = $state(0); function countClicks() { ++clickCount; } </script> <svelte:document onclick={() => countClicks()} /> <pre class="brush:php;toolbar:false">Clicks inside document: {clickCount}
clickCount 的反应性质不会通过使用赋值运算符转移或复制到 secondaryClickCount。如果符文是函数,那么上面的代码就可以工作,但它们不是。
关于 $state 还有一件更重要的事情需要提及:它使其值具有深度反应性。这意味着如果该值是一个属性包含对象的对象,那么所包含对象的属性也是反应性的。这种模式递归地应用,因此整个对象图最终都是反应性的。
组件属性预计是反应性的,Svelte v5 使用 $props rune 实现了这一点。
<script lang="ts"> let clickCount = $state(0); let secondClickCount = clickCount; function countClicks() { ++clickCount; } </script>
因为使用 TypeScript 是项目的一切,所以我们首先使用类型声明组件属性。可选属性通过附加 ? 进行标记。正如它的名字一样。
然后是符文的使用,这是一种解构语句。该示例展示了如何分配默认值以及如何允许“其余”属性(即任何其他属性)。该组件将属性的“其余”部分作为属性(属性)传播(应用)到 span HTML 元素上。
你可以让 props: Props = $props();定义属性并且它可以工作,但是您无法指定各种属性的默认值,因此我建议您始终按所示声明属性。顺便说一下,如果不解构的话,我也不知道如何声明restProperties。
如果您注意的话,上面的代码会产生 TypeScript 错误。毕竟,Props 类型没有提及任何“休息”属性。我们如何输入restProps?
一般来说,您可以执行以下操作来允许所有类型的操作。我想这取决于你的 TypeScript 技能。
以下打开 Props 类型以允许任何 data-* 属性:
<script lang="ts"> let clickCount = 0; function countClicks() { ++clickCount; } </script> <svelte:document on:click={() => countClicks()} /> <pre class="brush:php;toolbar:false">Clicks inside document: {clickCount}
这个允许任何事情:
<script lang="ts"> let clickCount = $state(0); function countClicks() { ++clickCount; } </script> <svelte:document onclick={() => countClicks()} /> <pre class="brush:php;toolbar:false">Clicks inside document: {clickCount}
但通常情况下,需要允许接收 restProps 的 HTML 元素的属性,在我们的示例中,这就是 span HTML 元素。
对于这种常见场景,Svelte v5 提供了应该覆盖大部分 HTML 元素的类型:
<script lang="ts"> let clickCount = $state(0); let secondClickCount = clickCount; function countClicks() { ++clickCount; } </script>
使用后者将使 VS Code 等 GUI 为 span HTML 元素的可能的 props(属性)提供准确的 Intellisense。不错吧?
HTMLAttributes
;接口用于属性列表中没有特殊性的 HTML 元素。然而,许多元素确实有。例如,不要执行 HTMLAttributes ,而是从 'svelte/elements' 导入 HTMLButtonAttributes 接口。
最后一个细节是默认值。其实没什么好说的,例子已经说明了一切:操作属性的默认值是“sum”。如果在使用组件时未指定该属性,则该属性将采用该值。
如果所需的默认值未定义,则根本不要指定任何内容。
这是一个非常特殊的符文,只能在组件属性中使用。它将属性标记为可绑定。
如果您不知道或不记得,Svelte 允许属性的 2 路绑定。 Vue 也有这个功能,而相比之下,React 没有。
使用方法超级简单:
<script lang="ts"> type Props = { data: number[]; operation?: 'sum', 'avg'; }; let { data, operation = 'sum', ...restProps, }: Props = $props(); function sum() { return data.reduce((p, c) => p + c); } function avg() { return sum() / data.length } </script> <span class="amount" {...restProps}>{operation === 'sum' ? sum() : avg()}</span> <style> .amount { font-family: monospace; } </style>
始终将其值修改为可绑定的属性,否则 Svelte 会发出控制台警告。该警告指出组件不应修改不属于它们的状态,如果有意这样做,则应使用绑定。
如示例所示,可以通过 $bindable rune 指定属性默认值。该示例将属性的默认值设置为 5。
但是默认值在这里有意义吗?嗯,是的。将属性声明为可绑定并不意味着它是必需的。
每当我们需要使用 props 或其他反应状态(可能随时间变化)的值来计算值时,我们都会使用 $衍生符文。
将计算总和和平均值的示例组件带回来,我们可以使用这个符文重写它:
<script lang="ts"> let clickCount = 0; function countClicks() { ++clickCount; } </script> <svelte:document on:click={() => countClicks()} /> <pre class="brush:php;toolbar:false">Clicks inside document: {clickCount}
现在我们有一个名为 result 的新变量,它与其输入一样具有反应性,并且每次数据数组中的数据发生变化时都会自动重新计算。它本身是一个反应变量,因此使用它的模板(组件的 HTML 部分)也会更新。
这个符文允许我们指定在反应数据发生变化时运行的任意代码。为了让这个符文发挥其魔力,它会跟踪在执行过程中读取的反应数据。然后,每当库存中的任何内容改变其值时,都会使用此反应数据库存来重新触发效果。
最常见的场景可能是根据更改的值重新触发数据获取操作:
<script lang="ts"> let clickCount = $state(0); function countClicks() { ++clickCount; } </script> <svelte:document onclick={() => countClicks()} /> <pre class="brush:php;toolbar:false">Clicks inside document: {clickCount}
每当我们不希望 $衍生变量持有承诺时,异步操作通常是内部效果的常态。但就我个人而言,由于在 Svelte 中使用 Promise 非常容易,所以我会简单地使用 $driven 值。接下来显示的变体使数据成为具有承诺的反应式计算值:
<script lang="ts"> let clickCount = $state(0); let secondClickCount = clickCount; function countClicks() { ++clickCount; } </script>
一般来说,如果您将 $state 和 $effect 结合起来,那么使用 $driven 可能会更好。不过,这条规则也有例外,所以请将其视为经验法则,而不是圣言。
如果数据获取不是 $effect 的一个很好的例子,那么什么才是呢?我们来看看这个:
<script lang="ts"> type Props = { data: number[]; operation?: 'sum', 'avg'; }; let { data, operation = 'sum', ...restProps, }: Props = $props(); function sum() { return data.reduce((p, c) => p + c); } function avg() { return sum() / data.length } </script> <span class="amount" {...restProps}>{operation === 'sum' ? sum() : avg()}</span> <style> .amount { font-family: monospace; } </style>
这是一个简单的计时器组件,通过其状态属性进行控制。这里使用 $effect 符文来强制计时器的操作。你能想象将其重构为 $衍生吗?顺便说一下,不要尝试,因为 elapsed 是一个 prop,所以它不能同时是 $driven 和 prop。
Svelte v5 配备了全新的反应性引擎,旨在实现更好的重新渲染性能和更好的代码重构。使用新的反应性系统既简单又复杂:简单是因为系统的设计很好地涵盖了常见场景,而困难一点是因为与 v4 相比,代码变得更加复杂。
无论如何,新系统功能强大,能够优雅而有效地满足大多数场景,为所有可能的可能性提供符文,并且易于使用,尽管一开始有点奇怪。
本文仅介绍了符文的介绍部分,以及一些个人使用符文的经验。还有更多主题可以帮助您(读者朋友)更快地掌握 Svelte 的新版本,即:
深入了解 $effect 的工作原理
高级符文($state.raw、$categories.by、$effect.pre 等)
用反应状态替换商店
异常情况
看看这些基准测试结果:交互式结果 (krausest.github.io)
现在,框架列表令人震惊,因此您可以复制以下 JSON,然后使用“粘贴”按钮将其粘贴到网页中(参见屏幕截图):
<script lang="ts"> let clickCount = 0; function countClicks() { ++clickCount; } </script> <svelte:document on:click={() => countClicks()} /> <pre class="brush:php;toolbar:false">Clicks inside document: {clickCount}
顺便说一句,我认为简单地聚焦窗口并通过键盘粘贴也可以。
这将框架列表缩小到更流行的框架,或者至少是我认为流行的框架。也许你比我更了解。
遗憾的是,Svelte v4 不再出现在图表中,但正如您所看到的,在选定的框架中,前 3 名是无可争议的:Vanilla JS、Solid 和 Svelte。
另一方面,看到 React v19 表现如此糟糕令人难过。编译器不是应该让它变得更好吗?看来最后的努力都是白费了。当然,它似乎优于 React v18,但仅此而已。我不确定为什么 Meta 继续在 React 上投资。大家有什么想法吗?
以上是学习新的 Svelte veactivity 系统的详细内容。更多信息请关注PHP中文网其他相关文章!