声明:本文为作者原创,如需转载请注明出处
Svelte 简介
Svelte 是一个由 Rich Harris(Rollup、Reactive.js 作者) 创建的开放源代码的前端编译器,与传统框架(如 React 和 Vue)在浏览器中进行大量处理的方式不同,Svelte 将这些工作提前到编译阶段处理。构建一个 Svelte 应用程序会生成操作文档对象模型(DOM)的代码,Svelte 借此可以减少传输的文件的大小,并提供更好的启动和运行时性能。Svelte 有由 TS 编写的编译器,用于在构建时将应用代码转换为客户端的 JS。
相关链接:Svelte 中文官网、 前端主流框架排名一览表
Svelte 优点
1. 语法精简,体积轻量
Svelte 提供了比 React 和 Vue 更加精简的语法,如自动的响应式状态和直观易用的模板语法和 API 等,这可以使开发者在短时间内快速上手进行开发;Svelte 包的体积仅有 1.6 KB,极其轻量,这对于小型项目很有吸引力。
2. 组件可跨框架使用
Svelte 本质上是一个前端编译器,它编译出来的是一个 JS 类,是不依赖任何框架的普通原生 JS 代码,因此其他框架可以对其进行引用从而实现 Svelte 组件跨框架的特性。
3. 无虚拟 DOM,性能优异
Svelte 的核心思想在于“通过静态编译减少框架运行时的代码量”,这是它与 React、Vue 等主流框架最大的不同之处。传统框架如 React 和 Vue 在浏览器中需要做大量的工作,而 Svelte 将这些工作放到构建应用程序的编译阶段来处理。当一个 Svelte 组件被编译完成以后,所有需要的运行时代码都包含在里面了,除了引入这个组件本身,我们不需要再额外引入一个所谓的框架运行时,自然也就不需要进行虚拟 DOM 的 diff/patch 操作,极大的减少了内存的占用,性能也就随之提高。
Svelte 缺点
1. 生态不够完善
Svelte 在 2016 年开始研发,2019 年才正式发布,整体相对较新,因此它的社区支持和生态系统还不够完善,这意味着我们在开发的时候可能会遇到一些新问题。
2. 可能不太适合大型项目
虽然包的体积小,但是编译后的代码其实并不小,代码总量的增加曲线稍显陡峭,在大型项目中有一些压力。
Svelte 基础语法
Svelte 语法与 Vue 类似,也是指令式、响应式的单文件组件,文件后缀为.svelte。以下为一个空的 Svelte 组件所包含的内容,后续内容均在此基础上进行增加:
<script>
//代码文件
</script>
<style>
/* 样式文件*/
</style>
<!-- 此处一般放置元素标签(多个或者为空) -->
1. 模板语法
1.1 文本插值
<h1>Hello {name}!</h1>
<p>{a} + {b} = {a + b}.</p>
1.2 HTML 插入
<div class="blog-post">
<h1>{post.title}</h1>
{@html post.content}
</div>
1.3 props
<!-- props 可以加引号也可以不加引号 -->
<button disabled="{number !== 42}">...</button>
<button disabled={!clickable}>...</button>
<!-- 两者是等价的,后者为前者的简写形式 -->
<button disabled={disabled}>...</button>
<button {disabled}>...</button>
<Widget foo={bar} answer={42} text="hello"/>
<Widget {...things}/>
<Widget {...$$props}/>
❗️注意:$$props可以传递所有props属性到一个组件中,包含未使用export声明的props属性。它在特殊情况下很有用,但通常不推荐使用,因为 Svelte 难以优化。
2. 响应式状态
<script>
let count = 0;
function handleClick() {
// 无需 setState
count += 1;
}
</script>
<button on:click={handleClick}>
Clicked {count} {count === 1 ? 'time' : 'times'}
</button>
❗️注意:在 Svelte 中,状态以及视图的更新是由赋值语句触发的,因此使用数组的诸如push和splice之类的方法就不会触发自动更新,解决该问题的方法是添加一个多余的赋值语句或先解构再赋值,相关代码如下所示:
// 方法一:添加一个多余的赋值语句
function addNumber() {
numbers.push(numbers.length + 1);
numbers = numbers;
}
// 方法二:先解构再赋值
function addNumber() {
numbers = [...numbers, numbers.length + 1];
}
3. 计算属性
<script>
let count = 0;
$: doubled = count * 2;
function handleClick() {
count += 1;
}
</script>
<button on:click={handleClick}>
{count} doubled is {doubled}
</button>
4. 类与样式绑定
4.1 style 动态绑定
<script>
let name;
</script>
<!-- 仅当 name 值为 JYP 时背景颜色显示为黄色,其余情况下为白色 -->
<div style="width: 200px; height: 200px; background-color: {name === 'JYP' ? 'yellow' : 'white'}"></div>
<input type="text" bind:value={name}>
4.2 class 动态绑定
<!-- 它们是等价的 -->
<div class="{active ? 'active' : ''}">...</div>
<div class:active={active}>...</div>
<!-- 简写 -->
<div class:active>...</div>
<!-- 可以包含多个class状态 -->
<div class:active class:inactive={!active} class:isAdmin>...</div>
❗️注意:在 Svelte 中,<style>标签块中的样式默认作用于组件内部,其中包含的样式仅对当前组件有效。此外,我们也可以使用:global(...)修饰符来添加全局样式。
<style>
:global(body) {
/* 这里样式全局应用于 <body>内都 */
margin: 0;
}
div :global(strong) {
/* 这里表示全局应用于被<div>包裹的<strong>标签 */
color: goldenrod;
}
</style>
5. 双向数据绑定
在 Svelte 中,双向数据绑定主要是通过bind:value这个指令来实现的。
<!-- 文本框 -->
<input bind:value={name}>
<!-- 文本域 -->
<textarea bind:value={text}></textarea>
<!-- 多选框 -->
<input type="checkbox" bind:checked={yes}>
<!-- 选择器 -->
<select bind:value={selected}>
<option value={a}>a</option>
<option value={b}>b</option>
<option value={c}>c</option>
</select>
<!-- 选择器简写形式 -->
<select bind:value={selected}>
<option>a</option>
<option>b</option>
<option>c</option>
</select>
6. 条件渲染
{#if porridge.temperature > 100}
<p>too hot!</p>
{:else if 80 > porridge.temperature}
<p>too cold!</p>
{:else}
<p>just right!</p>
{/if}
7. 列表渲染
{#each todos as todo, index (todo.id)}
<p>{todo.text}</p>
{:else}
<!-- 如果列表为空,则显示{:else} 条件下内容 -->
<p>No tasks today!</p>
{/each}
这里的todo.id是每个p元素的key。
8. 异步渲染
利用异步渲染,我们可以针对Promise的三个不同的状态(pending、 fulfilled 和 rejected)进行不同内容的渲染,可以用于AJAX异步请求接口的情形。
{#await promise}
<!-- promise 状态是“未决” -->
<p>waiting for the promise to resolve...</p>
{:then value}
<!-- promise 状态是 “完成” -->
<p>The value is {value}</p>
{:catch error}
<!-- promise 状态是“被拒绝” -->
<p>Something went wrong: {error.message}</p>
{/await}
9. 事件处理
在 Svelte 中使用on:指令来对 DOM 事件进行监听。
<script>
let count = 0;
function handleClick(event) {
count += 1;
}
</script>
<!-- 写法一 -->
<button on:click={handleClick}>
count: {count}
</button>
<!-- 写法二 -->
<button on:click="{() => count += 1}">
count: {count}
</button>
在 Svelte 中支持使用|字符为 DOM 事件添加修饰符,可以使用的修饰符有以下5个:
preventDefault:阻止默认事件stopPropagation:阻止事件冒泡passive:改善了 touch/wheel 事件的滚动表现(Svelte会在合适的地方自动加上它)capture:表示在事件捕获阶段而不是在事件冒泡阶段触发其程序once:程序运行一次后删除自身
事件修饰符可以串联在一起使用,比如on:click|once|capture={...}。
<form on:submit|preventDefault={handleSubmit}>
<!-- 阻止表单默认点击事件 -->
</form>
10. 生命周期
10.1 onMount
<script>
import { onMount } from 'svelte';
onMount(() => {
const interval = setInterval(() => {
console.log('beep');
}, 1000);
// 该回调函数在组件卸载时调用
return () => clearInterval(interval);
});
</script>
10.2 beforeUpdate
<script>
import { beforeUpdate } from 'svelte';
beforeUpdate(() => {
console.log('the component is about to update');
});
</script>
❗️注意:beforeUpdate的首次回调运行在onMount初始化之前。
10.3 afterUpdate
<script>
import { afterUpdate } from 'svelte';
afterUpdate(() => {
console.log('the component just updated');
});
</script>
10.4 onDestroy
<script>
import { onDestroy } from 'svelte';
onDestroy(() => {
console.log('the component is being destroyed');
});
</script>
11. 组件间通信
11.1 父传子
Father.svelte
<script>
import Child from "./Child.svelte";
</script>
<Child count={5} />
Child.svelte
<script>
export let count = 0;
const increment = () => {
count += 1;
}
</script>
<button on:click={increment}>
count is {count}
</button>
11.2 子传父
Child.svelte
<script>
import { createEventDispatcher } from 'svelte';
export let count = 0;
const dispatch = createEventDispatcher();
const increment = () => {
count += 1;
}
const notify = () => {
dispatch('notify',`按钮被点击了,此时 count 的值为:${count}`);
}
</script>
<button on:click={increment} on:click={notify}>
count is {count}
</button>
Father.svelte
<script>
import Child from "./Child.svelte";
const notify = (event) => {
console.log(event.detail);
}
</script>
<Child count={5} on:notify={notify} />
12. Slot
在 Svelte 中同样也支持 Slot(插槽)。
12.1 基本用法
<!-- App.svelte -->
<Widget></Widget>
<Widget>
<p>本段文本将会替代slot标签内的默认内容。</p>
</Widget>
<!-- Widget.svelte -->
<div>
<slot>
默认内容,component外部没有内容传入时显示本段文本。
</slot>
</div>
12.2 具名插槽
<!-- App.svelte -->
<Widget>
<h1 slot="header">Hello</h1>
<p slot="footer">Copyright (c) 2019 Svelte Industries</p>
</Widget>
<!-- Widget.svelte -->
<div>
<slot name="header">No header was provided</slot>
<p>Some content between header and footer</p>
<slot name="footer"></slot>
</div>
13. Context
Svelte 也提供了context对象用以实现多级组件间的跨级通信。
13.1 setContex
<script>
import { setContext } from 'svelte';
setContext('user', {
name: 'JYP',
age: 23,
});
</script>
13.2 getContext
<script>
import { getContext } from 'svelte';
const user = getContext('user');
const { name, age } = user;
</script>
<p>
My name is {name}, and I'm {age} years old.
</p>
❗️注意:setContex 和 getContext 方法均需在组件初始化期间调用,即在最上层调用。
14. Ref
实际上在 Svelte 中并没有Ref的说法,但是却提供了相同作用的指令:bind:this,我们可以使用该指令来获取当前组件实例。
<ShoppingCart bind:this={cart}/>
<!-- empty() 方法为 ShoppingCart 这个组件实例所有 -->
<button on:click={() => cart.empty()}>
清空购物车
</button>
15. 路由
官方提供的路由库是 SvelteKit,目前处于 beta 阶段,另外还可以使用第三方路由库,如:page.js、svelte-spa-router、svelte-routing、Routify 等。
16. 状态管理
Svelte 没有单独的的状态管理库,它已经内置了一种被称为store的状态管理工具,有关它的详细介绍可参阅 Svelte 官网。
总结
Svelte 是一个 Web 应用构建工具,它语法精简,易于上手,编译出来的项目体积小,性能强,无虚拟 DOM,但由于出现较晚,生态不够成熟,并且可能对大型项目难以应付,因此在开发中需要根据 Svelte 的优缺点进行取舍。
参考文章:如何看待 svelte 这个前端框架?、前端新宠 Svelte 带来哪些新思想?赶紧学起来!、这些前端新技术你很难再忽视了 —— Svelte、深入浅出svelte.js、Svelte 原理浅析与评测