经过近 18 个月的开发,数十名贡献者提交了数千次代码,Svelte 5 终于发布了稳定版,这是该项目历史上最重要的版本。
Svelte 是一款类似 React、Vue 的前端 UI 框架。
如名字所言, Svelte 的体积非常小,其作者 Rich Harris 表示,他设计 Svelte 的核心思想在于“通过静态编译减少框架运行时的代码量”。
Svelte 5 是一次彻底的重写:开发的应用将变得更快、更小、更可靠。同时将能够编写更一致、更符合语法习惯的代码。对于框架的新手来说,需要学习的东西更少。
值得关注的是,Svelte 几乎完全向后兼容 Svelte 4,也就是说初始升级将完全无缝:
Svelt 官方介绍,随着越来越多的人使用 Svelte 构建更多、更大的应用程序,最初的一些设计决策的局限性开始变得更加明显。
例如,在 Svelte 4 中,响应性完全由编译器驱动。如果在 Svelte 4 中更改响应对象的单个属性,则整个对象将失效,因为这是编译器实际能做的全部。
与此同时,其它框架采用了基于信号的细粒度响应性,超越了 Svelte 的性能。
同样,Svelte 4 中的组件组合也比较尴尬,主要是因为它将事件处理程序和“插槽内容” (slotted content) 视为独立的概念,与传递给组件的 props 不同。
这是因为在 2019 年,Web 组件似乎很有可能成为组件的主要分发机制,而 Svelte 希望与平台保持一致。现在看,这其实是一个错误选择。
虽然 $
的构造响应式重新运行语句是一个巧妙的技巧,但结果却是徒劳无功。它混淆了两个本应分开的概念(派生状态和副作用),而且由于依赖关系是在语句编译时而不是运行时确定的,因此它无法重构,搞得越来越复杂。
Svelte 5 消除了这些不一致和麻烦。它引入了 runes,这是一种显式机制,用于声明响应式状态(当然它还有其它作用):
与状态的交互没有改变:与其它框架不同,count 只是一个数字,而不是一个函数,或者具有值属性的对象,或者只能通过相应的 setCount 来更改的东西。
function increment() {
count += 1;
console.log({ count });}
runes 可以在 .svelte.js 和 .svelte.ts 模块中使用,除了 .svelte 组件,这意味着可以使用单一机制创建可重用的响应式逻辑。
事件处理程序现在就像其它属性一样,比如可以很容易知道组件用户是否提供了特定的事件处理程序(这对于避免昂贵的设置工作非常有用),或者将任意事件处理程序传播到某个元素上,这些对库作者尤其重要。
用于在组件之间传递内容的插槽机制,以及令人困惑的 let: 和 <svelte:fragment> 语法,已被 {#snippet …} 替代,这是一个更强大的工具。
除了这些变化,还有无数改进:原生 TypeScript 支持(不再需要预处理器!)、许多错误修复,以及整体性能和可扩展性的提升。
Svelte v4 的响应式写法:
<script>
let count = 0;
</script>
<button onclick={() => count++}>
clicks: {count}
</button>
<script>
let count = $state(0);
</script>
<button onclick={() => count++}>
clicks: {count}
</button>
• $state
• $state.raw
• $state.snapshot
• $derived
• $derived.by
• $effect
• $effect.pre
• $effect.tracking
• $effect.root
• $props
• $bindable
• $inspect
• $host
这不禁让我回想起了 10 年前写 PHP 的时候,满屏幕的 $
的日子。。。
$
。<script setup>import{ createReusableTemplate } from '@vueuse/core'; const[DefineTemplate,ReuseTemplate]= createReusableTemplate();</script>
<template>
<DefineTemplate>
<!-- something complex --></DefineTemplate>
<dialogv-if="showInDialog">
<ReuseTemplate/></dialog>
<divv-else>
<ReuseTemplate/></div>
</template>
{#each images as image} {# if image.href}
<a href={image.href}>
<figure>
<img
src={image.src}
alt={image.caption}
width={image.width}
height={image.height}
/>
<figcaption>{image.caption}</figcaption>
</figure>
</a>{:else}
<figure>
<img
src={image.src}
alt={image.caption}
width={image.width}
height={image.height}
/>
<figcaption>{image.caption}</figcaption>
</figure>{/if} {/each}
{#snippet figure(image)}
<figure>
<img
src={image.src}
alt={image.caption}
width={image.width}
height={image.height}
/>
<figcaption>{image.caption}</figcaption>
</figure> {
/snippet}{#each images as image} {#if image.href} <a href={image.href}>
{@renderfigure(image)}
</a>{:else} {@renderfigure(image)} {/if} {/each}
<script>
let count = $state(0);
</script>
<button on:click={() => count++}>
clicks: {count}
</button>
<script>
let count = $state(0);
</script>
<button onclick={() => count++}>
clicks: {count}
</button>
<button on:click={one} on:click={two}>
...
</button>
<button
onclick={(e) => {
one(e);
two(e);
}}
>
...
</button>
<button on:click|once|preventDefault={handler}>...</button>
<script>
functiononce(fn){
returnfunction(event){
if(fn) fn.call(this, event);
fn =null;
};
}
functionpreventDefault(fn){
returnfunction(event){
event.preventDefault();
fn.call(this, event);
};
}
</script>
<button onclick = {
once(preventDefault(handler))
} > ... < /button>
<script>
import {flushSync }from 'svelte';
let count = $state(0);
let element;
functiononclick(){
flushSync(() =>(count +=1));
// without `flushSync`, the DOM would be updated in the future
console.log(element.textContent===String(count));
}
</script>
<span bind: this = {element}>{count}</span><button {onclick}>update</button>
import {
mount
} from 'svelte';
importAppfrom './App.svelte';
const app = mount(App, {
target: document.querySelector('#app'),props: {some: 'property'}
});
import {
hydrate
} from 'svelte';
importAppfrom './App.svelte';
const app = hydrate(App, {
target: document.querySelector('#app'),props: {some: 'property'}
});
本文部分内容来自「陈随易」公众号。
相关来源
https://svelte.dev/blog/svelte-5-is-alive
https://mp.weixin.qq.com/s/J0fnK5ufOvH9_wyZUPPJ7Q