談談魔法消失UI框架 Svelte

最近基於公司業務需求,可能會要開發一款瀏覽器插件,調查後發現插件UI開發本質上就是開發頁面。因而我便開始尋找一個很是小又很是快的新玩具(工具)。畢竟前端 3 大框架不管哪個去開發瀏覽器插件都無異於大炮打蚊子。至於開發效率極低的 Dom 操做我也不想去碰了。因而我就找到了這個已經在國外很是火熱的魔法消失 UI 框架 —— Sveltehtml

Svelte是什麼

Svelte 是一個編譯型的前端組件框架。該框架沒有使用虛擬 dom,而是經過編譯在應用狀態發生改變時提供異步響應。前端

編譯型框架

任何前端框架都是有運行時的,(以 Vue 爲例) 該框架至少須要在瀏覽器攜帶虛擬dom 以及 diff 算法。若是在頁面中直接引入 Vue 腳本,還須要追加 Vue 前端編譯器代碼。能夠參考Vue 對不一樣構建版本的解釋vue

Svelte 則不一樣,它從開始就決定把其餘框架在瀏覽器所完成的大部分工做轉換到構建中的編譯步驟,以便於減小應用代碼量。它經過靜態分析來作到按需提供功能(徹底不須要引入),同時它也能夠分析得出根據你當前的修改精準更新 dom的代碼來提高性能。node

咱們以最簡單的代碼爲例子。git

// App.svelte
<h1>Hello world!</h1>

// main.js
import App from './App.svelte';

const app = new App({
    target: document.body
});

export default app;

實際上開發版會被編譯爲(爲了簡化,只分析部分,不分析所有代碼)github

// IIFE 當即執行函數表達式
var app = (function () {
  'use strict';
  // 空函數,用於某些須要提供函數的代碼
  function noop() { }  
  // 當前 元素所在的行 列 前面有多少字符等信息,開發版存在
  function add_location(element, 
                        file, 
                        line, 
                        column, 
                        char) {
    element.__svelte_meta = {
      loc: { file, line, column, char }
     };
  }
  //   
  // 操做dom輔助函數,減小代碼量...(至關於運行時)  
  function insert(target, node, anchor) {
    target.insertBefore(node, anchor || null);
  }
  function detach(node) {
    node.parentNode.removeChild(node);
  }
  function element(name) {
    return document.createElement(name);
  }
  function children(element) {
    return Array.from(element.childNodes);
  }
  // 異步處理修改過的組件
  // (實際上這些代碼在當前場景下不須要,能夠去除,但不必)
  const dirty_components = [];
  const binding_callbacks = [];
  const render_callbacks = [];
  const flush_callbacks = [];
  const resolved_promise = Promise.resolve();
  let update_scheduled = false;
  function schedule_update() {
    // ... 
  }
  function add_render_callback(fn) {
    // ...    
  }
  function flush() {
   // ...  
  }
  function update($$) {
  }
 
  // 當前文件,開發版
  const file = "src\\App.svelte";
  
  function create_fragment(ctx) {
    let h1;

    const block = {
      // 建立  
      c: function create() {
        h1 = element("h1");
        h1.textContent = "Hello world!";
        add_location(h1, file, 1, 9, 10);
      },
      // 掛載剛纔建立的元素
      m: function mount(target, anchor) {
        // 上面的 target 是 document.body  
        insert_dev(target, h1, anchor);
      },
      // 修改,髒組件在上述 update 中被調用 
      p: noop,
      // 刪除  
      d: function destroy(detaching) {
        if (detaching) detach_dev(h1);
      }
    };

    dispatch_dev("SvelteRegisterBlock", {
      block,
      id: create_fragment.name,
      type: "component",
        source: "",
        ctx
    });
    return block;
  }  
}());

能夠看到,在開發版本編譯完成後,你所寫的全部代碼都變成了原生的 js 操做Dom。同時具備很高的可讀性(這點很是重要)。我在個人另外一篇 blog 優化 web 應用程序性能方案總結 也代表了,提高代碼的覆蓋率是全部優化機制中收益是最高的。這意味着能夠加載更少的代碼,執行更少的代碼,消耗更少的資源,緩存更少的資源。同時在 Vue 3 中也會又靜態分析而進行的按需提供優化。web

值得一提的是,由於 Svelte 是編譯型框架,不管是開發仍是生產環境,都會在相同文件夾誕生一樣的文件(至少在筆者開始寫時候是這樣的結果,於2020-1-4)。若是前端沒有使用構建部署工具又或者像我這樣僅僅想要開發一個瀏覽器插件的狀況下,可能會形成由於文件夾已經存在而忘記進行構建命令,從而錯誤的使用開發版所產生的代碼。算法

同時,Svelte 直接支持各類編譯配置項目。只要在組件中添加 options 便可:編程

<svelte:options option={value}/>
  • immmutable 當你確認了當前已經使用不可變數據結構,編譯器會執行簡單的引用相等(而不是對象屬性)來肯定值是否更改,以便得到更高的性能優化,默認爲 false。當此選項設置爲true時,若是父組件修改了子組件的對象屬性,則子組件將不會檢測到更改而且也不會從新渲染。小程序

    <svelte:options immmutable={true}/>
  • tag 能夠將手寫的組件編譯成 Web Components 而讓其餘框架使用。根據以下就可使用。至於 Web Components 則能夠參考阮一峯的 Web Components 入門實例教程。這也是新框架不可或缺的功能點。

    <svelte:options tag="my-custom-element"/>
  • accessors 能夠爲組件的 props 添加 getter 和 setter,默認爲false。
  • namespace 是將要使用該組件的命名空間,一種用途是爲 SVG 指定命名空間。
當一門語言的能力不足,而用戶的運行環境又不支持其它選擇的時候,這門語言就會淪爲 「編譯目標」 語言

幾年前的 js 即是這樣的語言,當時有表現力更強的 coffeeScript,也有限制性更強的 Typescript。隨着時間的推移,前端的想要編譯的再也不侷限於編程語言層面上,而在框架層面也想作的更多。

自從 2018 年底,前端框架更多的往編譯型發展 。一種是爲了更強大的表現能力和性能增益,如同 Svelte 通常, 另外一種則是爲了抹平多個平臺的差距, 例如 國內的各個小程序框架 Taro(React 系,適合新項目), Mpx(微信小程序,適合老項目)等。

更高的表現力

對比同類型的 Elm Imba 以及 ClojureScript 這些編譯型框架,不管是工具鏈仍是語法表現力, Svelte 的對於前端小夥伴們友好度是最高的。若是想要學習 Svelte,能夠去看官網提供的 tutorial 模塊,Svelte 官網對於新手的友好度也是很是棒的。下面則介紹一些特定的語法。

狀態與雙向數據綁定

利用 let 能夠設置狀態,利用bind:value能夠進行數據綁定。

<script>
  // 直接使用 數據
  let name = 'world';
  // 配置項,能夠直接傳入 input
  const inputAttrs = {
    // input 類型爲 text  
    type: 'text',
    // 最大長度
    maxlength: 10
  };
</script>

<!-- 名字, 同時傳遞屬性能夠利用 ...語法來進行優化 -->
<input {...inputAttrs} bind:value={name} />
<hr/>
Hello {name}

能夠看到, 上述代碼很容易進行了雙向數據綁定,以及很是強大的代碼表現能力。

屬性的使用

父組件:

<script>
  import Hello from './Hello.svelte';
</script>
 
<Hello name="Mark" />

Hello 組件:

<script>
  // 這樣即是能夠被外部接受,可是name也能夠被內部修改
  export let name = 'World';
  // 計算屬性,name 修改了,doubleName 發生改變
  $: doubleName = name + name
</script>
 
<div>
  Hello, {name}!
</div>
<hr/>
{doubleName}

同組件直接的交互

這個功能就是最吸引個人地方,我用過不少組件框架。但對於同組件之間的交互都是要寫到父組件中做爲業務類型組件。例如地址之間的交互(默認地址),複雜表單之間的交互, 代碼以下所示:

Input 組件

<!-- 模塊 -->
<script context="module">
    // 組件全局 Map
    const map = new Map();
    
    // 清楚全部的輸入數據,導出函數
    export function clearAll() {
        map.forEach(clearfun => {
            clearfun()
        });
    }
</script>

<script>
    import { onMount } from 'svelte';
    // 記錄的標籤
    export let index;
    let value = ''

    // 掛載時候把當前組件的標籤和函數放入map
    onMount(() => {
        map.set(index, clear);
    });

    // 把當前 input 元素的數值清空
    function clear  ()  {
        value = ''
    }

    // 輸入時候把其餘的數據清除
    function clearOthers() {
        map.forEach((clearfun, key) => {
            if (key !== index) clearfun();
        });
    }
</script>


<div>
<button on:click={clear}>清楚當前輸入數據</button>
 <input on:input={clearOthers} type="text" bind:value={value}>
 {value}
</div>

App 組件:

<script>
    import Input, { clearAll } from './Input.svelte'
</script>
<div>
  // 能夠清楚子組件輸入的所有數據
  <button on:click={clearAll}>清楚所有數據</button>
  <Input index='1'/>
  <Input index='2'/>
  <Input index='3'/>
  <Input index='4'/>
  <Input index='5'/>
</div>

如此,同組件內之間的交互便完成了,很是的簡單,可是又是由於 module 全局性的,不管是否在同一父組件內,全部的子組件都會有全局的功能。若是有兩個以上的模塊在同一頁面中,又須要添加多餘的屬性來爲輔助開發。因此這個功能是一把可能會傷到本身的利器。

其餘

Svelte 有許多簡單好用的語法以及動畫,這裏就不一一介紹了。由於實在是太簡單了,若是你使用過其餘類型的框架,可能不到幾小時就能夠上手寫業務代碼了。固然,若是在開發插件過程當中遇到一些不可避免的問題,我也會記錄下來再寫一篇 blog 。

無可避免的缺點

Svelte 已經開發到 3.0 版本後了,大體上來看,一些開發上的問題可能沒有,可是畢竟沒有大公司支持,因此可能仍是會有一些不可避免的缺陷。

  • 不支持 TypeScript
  • 生態環境不夠好
  • 單組件文件後綴名爲 svelte,過於冗長
  • 暫時沒有構建工具
  • if 判斷 for 循環 很差用(爲了編譯)

    {#if user.loggedIn}
        <button on:click={toggle}>
            Log out
        </button>
    {/if}
    
    {#if !user.loggedIn}
        <button on:click={toggle}>
            Log in
        </button>
    {/if}
    
    {#each cats as { id, name }, i}
      <li>
        <a target="_blank" href="https://www.youtube.com/watch?v={id}">
          {i + 1}: {name}
        </a>
      </li>
    {/each}

鼓勵一下

若是你以爲這篇文章不錯,但願能夠給與我一些鼓勵,在個人 github 博客下幫忙 star 一下。
博客地址

參考文檔

Svelte 官網

相關文章
相關標籤/搜索