初探Svelet框架

623 阅读5分钟

背景

  • 本想是探索下 bytemd 框架是是如何实现左右两边内容同时滚动的,恰巧框架用的是Svelet框架来写的。
  • 也想学习使用一下不同的框架,做一期关于框架之间的对比,主要探讨下各框架的优势与劣势,做技术时我们如何做框架选择。

本文章作为上面两个知识的铺垫,先能够把Svelet框架运行起来。

环境搭建

因为我们研究的是 bytedance/bytemd , 所以选择和框架一样的技术选项。

框架使用的是 Vite + Svelet + TS。 OK,为了不出错,我们使用vite官网提供的模版,一股脑儿执行下面操作

// 选择我们所需要的模块创建出来即可
pnpm create vite

// 进入到我们创建的项目目录
cd swift-editor

// 安装库
pnpm install

// 启动项目
pnpm run dev

//http://localhost:5174/ 就可以访问

添加 可以用来把 里的内容转换为普通的CSS的库

import { defineConfig } from 'vite'
import { svelte } from '@sveltejs/vite-plugin-svelte'
import sveltePreprocess from 'svelte-preprocess' // 添加的

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [svelte({
    preprocess: [sveltePreprocess({ typescript: true })], 
  })],
})

Svelet框架简介

本章节内容如果你有react 或者 vue 框架经验 会更加容易理解。 如果看上去比较晦涩跳过也没关系,在后续其他文章会补充框架之间的横向对比,会介绍的更加详细。

先作者介绍:

Svelte是一个新兴的热门前端框架,作者是 Rich Harris,被称为前端界的【轮子哥】,有Ractive、Rollup 和 Buble开源作品。它被预测为未来十年可能取代React和 Vue 等其他框架的新兴技术。

为什么会说快呢?

写过React、Vue 的设计思路比较了解的话就会知道,他们必须引入运行时 (runtime) 代码。需要通过运行时代码在前端完成VirtualDom的构建,以及节点上状态的绑定,当状态发生改变时还要进行diff更新页面。

这些带了一个弊端:随着页面增大导致页面逻辑越来越复杂,页面出现性能变低情况。

为了缓解这些状况,React 和 Vue 分别做了升级优化:

  • React: 当初设计hooks的时候,当状态发生变化,我就是函数从上到下全部执行的。 🤔.... 这样吧

    • 这样 我给开发者提供更多的hooks。例如:useCallback,useMemo,shouldComponentUpdate 等,哈哈,这不明摆着把优化的锅甩给了开发者么,开发者的心智越来越重。【React的历史包袱也太重】
    • 那我再优化下diff算法,提出Fiber概念(本质上做了最小计算单元,防止页面卡顿)。在React 16 中引入的新的架构,将原本同步不可中断的更新,变成异步可中断更新,将原本一个耗时的大任务做了时间分片,拆分成一个个小任务,在浏览器空闲的时间执行。 【其实也能理解,用的JSX语法,只能在算法上做功课】
  • Vue3: 轮到尤大大了,思考了片刻,我也有Runtime,可是我用的是Template语法,我在运行时性能干不过你,但是我在预编译阶段可以通过Template语法 对VitualDom做剪枝优化。所以Vue3在预编译节点对没有变量以来的节点设置 paseFlag 为静态的,这样diff阶段工作量就小了。 【那种中国文化一下子有了,你还很难挑出他的毛病】

轮子哥: 看我的,嘿嘿... Runtime拖累性能,干脆我就不要了。

Svelte 的设计思路是【通过静态编译减少框架运行时的代码量,即预编译】,Svelted完全溶入JavaScript,应用所有需要的运行时代码都包含在bundle.js里面,因此不需要额外在引入运行时。

Svelte是一种新的思想设计,但是缺点也是有的:

  • 作为一个尚处在起步阶段的框架,Svelte 还有很多的不足,如果是在大型的商业项目中中使用 , 需要特别的谨慎。 【生态不够】

注:更多的优点和缺点读者可以去搜了解更多,这里只是带着大家去体验一下新的框架,拓宽下自己知识面。

基础用法

作者用了1小时左右时间将 入门教程 中的Demo挨着看了一遍。建议读者感兴趣的话,也可以做一遍,下面我们只将一些重点

1.基本用法

在Svelte应用中,一个.svelte就是一个组件,它由html、css和js代码组成,类似Vue的写法。这样做有利于降低开发的学习成本,但事实上很多点和Vue是相似的

<script lang="ts">
  let name = 'Rock';
</script>

<h1>Hello {name.toUpperCase()}!</h1>

可以向其中添加一些样式

<style>
  h1 {
    color: purple;
    font-family: 'Comic Sans MS', cursive;
    font-size: 2em;
}
</style>

组件嵌套

import Nested from './Nested.svelte';

2. reactivity

反应能力:能够让 DOM 与你的应用程序状态保持同步。 从下面的例子中可以看出Svelet极大的简化了写法。

<script>
let count = 0;
function handleClick() {
  count += 1;
}
</script>

<button on:click={handleClick}>
    Clicked {count} {count === 1 ? 'time' : 'times'}
</button>

反应式声明(对应Vue的计算属性)

<script>
    let count = 0;
    $: doubled = count * 2;
    function handleClick() {
        count += 1;
    }
</script>

<p>{count} doubled is {doubled}</p>
<button on:click={handleClick}>
    Clicked {count} {count === 1 ? 'time' : 'times'}
</button>

$: doubled = count * 2; 当count的值发生变化时,doubled 的值会自动的更新。

反应式语句(自动收集依赖,执行对应的语句)

<script>
    let count = 0;
    $: console.log(`the count is ${count}`);
    function handleClick() {
            count += 1;
    }
</script>

<button on:click={handleClick}>
    Clicked {count} {count === 1 ? 'time' : 'times'}
</button>

更新数组和对象需要注意:

由于 Svelte 的反应性是由赋值语句触发的,因此使用数组的诸如 push splice 之类的方法就不会触发自动更新。 这里重点提一下和Vue是不相同的。

3.属性

属性的传递 父 -> 子

// 子组件中 Nexted.svelte
<script>
   export let answer; // 直接不?! 和Vue中作为函数的参数是不是简单了很多
</script>

<p>The answer is {answer}</p>

//App.svelte
<script>
   import Nested from './Nested.svelte';
</script>

<Nested answer={42}/>

属性的传递是不是更加简单了。

如果你需要引用传递到组件中的所有参数,包括未使用export声明的道具,可以利用$$props 直接获取。但通常不建议这么做,因为Svelte难以优化,但在极少数情况下很有用。

4.逻辑

基础的语法

// if语法 else语法
{#if user.loggedIn}
    <button on:click={toggle}>
        Log out
    </button>
{/if}

// 遍历语法
<ul>
  {#each cats as cat (cat.id)}
    <li><a target="_blank" href="https://#?v={cat.id}">
        {cat.name}
    </a></li>
  {/each}
</ul>
  • 同样的如果使用遍历,也同样是建议给节点加上一个id

Await块

{#await promise}
        <p>...waiting</p>
{:then number}
        <p>The number is {number}</p>
{:catch error}
        <p style="color: red">{error.message}</p>
{/await}

promise 是一个异步调用函数。 如果是在Vue中写法,会多借助一个Reactive变量,Loading状态。这种写法就可以完全避免掉融于的变量了。

5.事件

通过on指令给jNode绑定事件

<script>
let m = { x: 0, y: 0 };

function handleMousemove(event) {
    m.x = event.clientX;
    m.y = event.clientY;
}
</script>

<style>
   div { width: 100%; height: 100%; }
</style>

<div on:mousemove={handleMousemove}>
    The mouse position is {m.x} x {m.y}
</div>

这点和Vue的操作也很像,甚至事件的修饰符基本上都是一致的

所有修饰符列表:

  • preventDefault :调用event.preventDefault() ,在运行处理程序之前调用。比如,对客户端表单处理有用。

  • stopPropagation :调用 event.stopPropagation(), 防止事件影响到下一个元素。

  • passive : 优化了对 touch/wheel 事件的滚动表现(Svelte 会在合适的地方自动添加滚动条)。

  • capture — 在 capture 阶段而不是bubbling 阶段触发事件处理程序 (MDN docs)

  • once :运行一次事件处理程序后将其删除。

  • self — 仅当 event.target 是其本身时才执行。

子组件怎么给父组件通信?

// App.svelte
<script>
import Inner from './Inner.svelte';
function handleMessage(event) {
   alert(event.detail.text);
}
</script>
<Inner on:message={handleMessage}/>

// Inner.svelte
<script>
import { createEventDispatcher } from 'svelte';
const dispatch = createEventDispatcher();
function sayHello() {
    dispatch('message', {
        text: 'Hello!'
    });
}
</script>
<button on:click={sayHello}>
    Click to say hello
</button>

哈哈,通过事件事件方式实现实现通信的。

  • 与 DOM 事件不同, 组件事件不会 冒泡(bubble) ,如果你想要在某个深层嵌套的组件上监听事件,则中间组件必须 转发(forward) 该事件。

6.绑定

数据流部分和Vue是一样的。

一般来说,Svelte 中的数据流是自上而下的——父组件可以在子组件上设置 props,组件可以在元素上设置属性,但反之则不然。 有时打破这个规则是有用的。 以该组件中的 元素为例 - 我们可以添加一个 on:input 事件处理程序,将 name 的值设置为 event.target.value。 正如我们将看到的,对于其他表单元素,情况会变得更糟。

<input bind:value={name}>

这种绑定方式写过Vue应该很熟悉,这种双向绑定其实数据还是单向的,只是框架屏蔽了数据相互传递的细节。 这一块就不多讲了。

  • this可以绑定到任何标签 (或组件) 并允许你获取对渲染标签的引用。 例如,我们对<canvas> 标签进行绑定:
<canvasbind:this={canvas}width={32}height={32}></canvas>

注意,canvas的值 undefined 会一直存在直至组件挂载完毕,因此我们给 onMount添加一个生命周期函数来处理。

  • 组件上的事件是通过子组件的向上 dispatch 来实现的。

7. 生命周期

每个组件都会有自己生命周期。

  • onMount: which runs after the component is first rendered to the DOM.
  • onDestory: To run code when your component is destroyed, use onDestroy.
  • beforeUdpate & afterUpdate: beforeUpdate 函数实现在DOM渲染完成前执行。afterUpdate函数则相反,它会运行在你的异步数据加载完成后。
  • tick函数不同于其他生命周期函数,因为你可以随时调用它,而不用等待组件首次初始化。它返回一个带有resolve方法的 Promise,每当组件pending状态变化便会立即体现到DOM中 (除非没有pending状态变化)。

这5个生命周期和Vue也基本相似的

8.Store & Context

Svelte自带了Store,用于解决跨组件的复杂状态传递,关于Store这里不介绍很多,和VueX类似。对于复杂的应用Store还是很有的, 以及Context 这里作为入门就都不太深入讲解。

9. 动画

  • 运动
  • 过度
  • 指令

这块也不作为入门进行介绍,感兴趣的同学可以自行进行学习。

10.Slot 插槽

插槽部分也存在具名插槽,跟Vue的方式相似。

11. 特殊标签

  • <svelte:self>: 因为不是函数式组件,没办法通过名字自己调用自己。因此发明了这个标签。

  • <svelte:component>:可以根据名称选择组件

  • <svelte:window >:如同事件可以监听 DOM 标签一样,你可以通过window 对象给 <svelte:window>标签添加事件监听

小结

作为新前端框架:

  • 使用shadow等高级特性,导致兼容性会出现一些问题。
  • 生态不是很完善,配套的安全、性能测试、自动化等工具不是很完善。
  • 语法和功能与Vue还是有很大的相似的,成本应该还好。
  • 作为开发者,鼓励多去尝试,还是有很多优点的。