最近这些年,随着React、Vue、Angular三大框架逐渐稳定,前端技术栈的迭代似乎也渐渐缓慢下来。并且随着React 16版本推出 Fiber, Vue 3.0 版本的正式发布,前端三大框架都有了自己的护城河。
不过话说回来,十年前我们谁会想到前端也会自成一派,变得如此智能。试想一下,如果我们把目光拉伸到未来十年,前端行业会出现怎么样的发展呢,会不会有挑战React或者Vue的新技术出现呢? 对于未来的发展,或许我们无从知晓,但是我们今天给大家推荐的Svelte 或许会是一个不错的挑战者。
一、Svelte简介
Svelte是一个新兴的热门前端框架,作者是 Rich Harris,被称为前端界的【轮子哥】,有Ractive、Rollup 和 Buble开源作品。
在官方的介绍中,Svelte 即是一个前端 UI框架,同时也是一个 编译器。在《State of JS survey of 2020》报告中,它被预测为未来十年可能取代React和Vue等其他框架的新兴技术。在开源托管网站Github上,Svelte也获得了超过61k的关注,这仅次于明星框架React和Vue。
在最新的开发者感兴趣的前端框架中,Svelte更是超过传统的知名框架Vue和React,排在第一位。
不过,这倒并不是说Svelte有多厉害,因为Svelte 当前仍是一个小众的开发框架,市场占有率方面也仍小于React和Vue,不过进步是特别明显的。
二、Svelte的优点
事实上,作为一个前端框架,Svelte在语法、使用体验上没有什么特别之处。真正不同的地方,是Svelte对前端AOT(ahead-of-time,可以理解为预编译)的探索。
如果大家对React、Vue 的设计思路比较了解的话就会知道,他们必须引入运行时 (runtime) 代码,用于虚拟dom、diff 算法。而Svelte 的设计思路是【通过静态编译减少框架运行时的代码量,即预编译】,Svelted完全溶入JavaScript,应用所有需要的运行时代码都包含在bundle.js里面,因此不需要额外在引入运行时。
2.1 No Runtime
React 和 Vue 都是基于运行时的框架,当用户操作页面进行各种操作改变组件的状态时,框架的运行时会根据组件状态(state)计算(diff)出哪些DOM节点需要被更新,从而更新视图。这就意味着,基于运行时框架本身所依赖的代码也会被打包到最终的构建产物中,结果是不可避免增加了打包后的体积。下图是常见的前端框架运行时的大小。
可以看到,最小的Vue有58k、React则有97.5k。 所以,如果我们如果使用React开发一个小型组件,即使里面的逻辑代码很少,但是打包出来的bundle size也会超过100k。对于大型后台管理系统来说100k 不算什么,但是对于特别注重用户端加载性能的场景来说,一个组件已经足够大了。
下面是Jacek Schae大神使用市面上主流的框架编写同样的Realword 应用,然后最终打包发布的体积。
可以看到,Svelte的bundle包的大小是Vue的1/4,React的1/20,体积上的优势还是相当明显的。
2.2 Less-Code
并且,编写同样的组件时,和 Vue 、React相比,Svelte只需要更少的代码。比如,React 官方的加法的示例代码:
//React
const [count, setCount] = useState(0);
function increment() {
setCount(count + 1);
}
而如果使用Svelte来实现的话,代码量明显变少。这是因为Svelte可以直接使用赋值操作符更新组件的状态。
//Svelte
let count = 0;
function increment() {
count += 1;
}
如果说上面的例子太简单了,不足以看出效果。那么下面还是使用Jacek Schae 编写Realword 应用代码行数的统计来说明。
2.3 Hight-Performance
在Virtual Dom已经是前端框架标配的今天, Svelte 声称自己是没有使用Virtual Dom, 那他是怎么还能保证高性能的呢?
下面是Jacek Schae 在《A RealWorld Comparison of Front-End Frameworks with Benchmarks》一文中使用主流的前端框架来编写 RealWorld 应用,使用 Chrome 的Lighthouse Audit测试性能,得出数据是Svelte 略逊于Vue,但好于 React。
而使用 JavaScript Framework Benchmark工具来分析各个框架的执行时间、内存占用及启用时间也给出了同样的答案。
在执行速度的方面,经过多次测试,Svelte 速度最快,Vue 紧随其后,React 和 Angular 速度较慢。
而在内存占用方面,Svelte 仍然保持大幅度领先,Vue 略微优于并驾齐驱的 React 和 Angular。
在启动方面,Svelte 的启动速度也非常出色,Vue 略逊一筹,React 和 Angular 紧随其后。 可以看到,在性能方面,Svelte并不逊色其他框架。
三、Svelte的缺点
当然,作为一个尚处在起步阶段的框架,Svelte 还有很多的不足,如果是在大型的商业项目中中使用 , 需要特别的谨慎。例如,下面是不同策略对代码生成量的影响。
根据尤大的测试,Svelte 是通过生成命令式的一个一个节点,然后把节点拼接这些 Javascript 代码。那这个策略就导致同等的这个组件源码之下 Svelte 每个组件的编译输出会更臃肿。虽然大家会第一印象是觉得说 Svelte 是以轻量而出名的,但其实我们会发现,在相对大型的项目中,在项目中组件超过 15 个之后,Svelte 的整体的打包体积优势就已经几乎已不存在。
除此之外,Svelte的缺点还包括:没有像AntD那样成熟的UI库。不支持预处理器,比如说less/scss,需要自己单独的配置 webpack loader等。不过,可以看到,Svelte正在快速的更新,最新版本解决的问题也不少。
四、快速上手
4.1 创建项目
和其他前端框架一样,创建一个Svelte项目是非常简单的,命令如下。
npm create svelte@latest my-app
cd my-app
npm install
npm run dev
然后在浏览器中打开 http://localhost:5173/ 就能访问对应的页面,运行的效果如下图。
如果需要修改端口号,可以打开package.json 文件,然后在启动命令里修改环境变量 PORT。
"scripts": {
"dev": "PORT=4000 rollup -c -w",
},
4.2 less配置
创建Svelte项目的时候,模板本身是不携带任何插件的,如果需要在 Svelte 组件中写 less,需要安装相关的依赖。
npm install svelte-preprocess-less less
然后,在 rollup.config.js 中添加相关的配置,如果没有 rollup.config.js 文件,可以新建一个。
import sveltePreprocess from 'svelte-preprocess';
import { less as svelteLess } from 'svelte-preprocess-less';
export default {
plugins: [
svelte({
preprocess: sveltePreprocess({
style: svelteLess(),
}),
}),
],
};
接下来,我们就可以在组件中的
<style lang="less">
...
</style>
五、语法基础
5.1 基本用法
在Svelte应用中,一个.svelte就是一个组件,它由html、css和js代码组成,类似vue的写法。其中,
<script>
let src = 'image.gif';
let name = 'Rick Astley';
</script>
<img {src} alt="{name} dances.">
当属性名和变量名是一样的时候,我们也可以简写省略掉变量名。而样式,和其他的写法是一样的。
<style>
.counter {
display: flex;
border-top: 1px solid rgba(0, 0, 0, 0.1);
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
margin: 1rem 0;
}
</style>
不过,上面说的例子都是简单的一个小组件,对于一个完整的应用程序来说,必然是由多个组件构成的。和其他的框架一样,使用时需要import引入进来,不同之处在于,import需要写在
<script>
import Counter from '$lib/Counter.svelte';
</script>
<section>
<Counter />
</section>
5.2 响应式
响应式也是Svelte的核心特性之一,在js里直接修改绑定的变量,就可以同步看到DOM上数据的改变。
<script>
let count = 0;
function handleClick() {
count++;
}
</script>
<button on:click={handleClick}>
Clicked {count} {count === 1 ? 'time' : 'times'}
</button>
类似vue里的computed,这里叫【反应式声明】,这样写:
let count = 0;
$: doubled = count * 2;
然后,就可以在众像用count那样使用doubled。Svelet的响应式是有赋值语句触发的,所以像数组的push、splice这些操作就不会触发更新,正确的做法是需要手动添加一个看似多余的赋值语句,比如。
<script>
let numbers = [1, 2, 3, 4];
function addNumber() {
numbers.push(numbers.length + 1);
numbers = numbers;
// 或者写成
// numbers = [...numbers, numbers.length + 1];
}
$: sum = numbers.reduce((t, n) => t + n, 0);
</script>
<p>{numbers.join(' + ')} = {sum}</p>
<button on:click={addNumber}>
Add a number
</button>
5.3 属性传值
在前端框架中,组件之间的传值一般使用的是构造函数。在Svelte中,组件之间的传值也比较简单,不过需要额外在子组件里,使用export关键字将值传递出去。
<Nested answer={21}/>
//子组件使用export导出
<script>
export let answer;
</script>
<p>The answer is {answer}</p>
5.4 逻辑语句
和其他的框架不同,Svelte的逻辑语句需要在HTML里面处理,比如{#if xxxxx},语法方面感觉比不是很友好。
{#if user.loggedIn}
<button on:click={toggle}>
Log out
</button>
{/if}
而对于if-else的写法,如下。
{#if x > 10}
<p>{x} is greater than 10</p>
{:else if 5 > x}
<p>{x} is less than 5</p>
{:else}
<p>{x} is between 5 and 10</p>
{/if}
其中,#表示一个块逻辑的开始,/表示结束,:表示继续。
如果要进行循环,一般使用的是for/each。不过,Svelte的循环语句实在让人难以接受。
<script>
let cats = [
{ id: 'J_aiyznGQ', name: 'Keyboard Cat' },
{ id: 'z_AbfPXTKms', name: 'Maru' },
];
</script>
<h1>The Famous Cats of YouTube</h1>
<ul>
{#each cats as cat,index}
<li><a target="_blank" href="https://www.youtube.com/watch?v={cat.id}">
{index+1} {cat.name}
</a></li>
{/each}
</ul>
5.5 事件
和其他框架一样,Svelte提供了on:click,on:mousemove等指令来监听事件。
<button on:click={() => (count += 1)} aria-label="Increase the counter by one">
<svg aria-hidden="true" viewBox="0 0 1 1">
<path d="M0,0.5 L1,0.5 M0.5,0 L0.5,1" />
</svg>
</button>
当然,事件监听的时候也可以使用事件修饰符,用‘|’分隔,表示可以连续使用多个。
on:click|once|capture={handleEvent}
特别需要注意的一点是,如果子组件内部想要接受父组件的点击事件,只需要在子组件内部加上on:click即可。
//父组件
<script>
import FancyButton from './FancyButton.svelte';
function handleClick() {
alert('clicked');
}
</script>
<FancyButton on:click={handleClick}/>
//子组件
<button on:click>
Click me
</button>
5.6 事件绑定
Svelte里的数据绑定和Vue、React时类似的,使用的是bind:value方式进行绑定。例如,下面是input标签的事件绑定。
<script>
let name = 'world';
</script>
<input bind:value={name}>
<h1>Hello {name}!</h1>
上述是单个值的绑定,那么绑定多个值的时候,可以用bind:group将value放在一起。
<label>
<input type=checkbox bind:group={flavours} value={flavour}>
</label>
并且,bind:this可以绑定任何标签或者组件,并且可以获得标签的引用,类似于React的ref。
<canvas
bind:this={canvas}
width={32}
height={32}
></canvas>
onMount(() => {
const ctx = canvas.getContext('2d');
.....
}
组件的属性也可以绑定,比如在父组件引用子组件的属性。
<Keypad bind:value={pin} on:submit={handleSubmit}/>
不过,作为一款年轻的前端框架,很少能够看到一些互联网公司将 Svelte 应用于生产,究其原因,无外乎以下几点:
- 对低端手机支持不太友好,特别是用shadow等高级特性。
- 生态不是很完善,配套的安全、性能测试、自动化等工具不是很完善。
- 全新的语法,需要一定的学习成本。
参考: