前端“新锐”——浅谈 Svelte

402 阅读5分钟

声明:本文为作者原创,如需转载请注明出处

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,极其轻量,这对于小型项目很有吸引力。

相关链接:Svelte、React、Vue 代码量比较

2. 组件可跨框架使用

Svelte 本质上是一个前端编译器,它编译出来的是一个 JS 类,是不依赖任何框架的普通原生 JS 代码,因此其他框架可以对其进行引用从而实现 Svelte 组件跨框架的特性。

3. 无虚拟 DOM,性能优异

Svelte 的核心思想在于“通过静态编译减少框架运行时的代码量”,这是它与 React、Vue 等主流框架最大的不同之处。传统框架如 React 和 Vue 在浏览器中需要做大量的工作,而 Svelte 将这些工作放到构建应用程序的编译阶段来处理。当一个 Svelte 组件被编译完成以后,所有需要的运行时代码都包含在里面了,除了引入这个组件本身,我们不需要再额外引入一个所谓的框架运行时,自然也就不需要进行虚拟 DOM 的 diff/patch 操作,极大的减少了内存的占用,性能也就随之提高。

相关链接:Svelte、React、Vue、Angular 性能测试

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 中,状态以及视图的更新是由赋值语句触发的,因此使用数组的诸如pushsplice之类的方法就不会触发自动更新,解决该问题的方法是添加一个多余的赋值语句或先解构再赋值,相关代码如下所示:

// 方法一:添加一个多余的赋值语句
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.jssvelte-spa-routersvelte-routingRoutify 等。

16. 状态管理

Svelte 没有单独的的状态管理库,它已经内置了一种被称为store的状态管理工具,有关它的详细介绍可参阅 Svelte 官网。

总结

Svelte 是一个 Web 应用构建工具,它语法精简,易于上手,编译出来的项目体积小,性能强,无虚拟 DOM,但由于出现较晚,生态不够成熟,并且可能对大型项目难以应付,因此在开发中需要根据 Svelte 的优缺点进行取舍。

参考文章:如何看待 svelte 这个前端框架?前端新宠 Svelte 带来哪些新思想?赶紧学起来!这些前端新技术你很难再忽视了 —— Svelte深入浅出svelte.jsSvelte 原理浅析与评测