[譯]svelte入門

一. 簡介

svelte類似于Vue,是一門純前端框架.區(qū)別于Vue/React,用svelte的話說就是頁面視圖的更新并不是采用類似虛擬DOM的方式.另外傳統(tǒng)的前端框架一般直接返回js文件由瀏覽器編譯成頁面視圖,而svelte編譯視圖的過程則在構(gòu)建編譯時完成,大大減輕了瀏覽器的負擔.

原文: Instead of using techniques like virtual DOM diffing, Svelte writes code that surgically updates the DOM when the state of your app changes.

本文主要是對svelte官網(wǎng)中的教程進行翻譯,以便快速了解svelte的一些基礎知識

初始化項目

npm create svelte@latest myapp
cd myapp
npm install
npm run dev

建議使用Vite/Vite-plugin-svelte構(gòu)建工具, 具體可了解(https://sveltesociety.dev/tools)
SvelteKit是用來專門構(gòu)建svelte應用的腳手架(項目構(gòu)建工具), 但目前正在開發(fā)中, 并不建議使用
與Vue相類似,svelte的組件文件是名為.svelte結(jié)尾的文件
注意的是,組件的導入并不需要額外注冊即可使用, 類似于Vue3的setup語法糖

二. 語法

2.1 頁面賦值

定義一個變量, 在視圖中用 {} 進行賦值

<script>
    let name = 'world';
</script>

<h1>Hello {name}!</h1>

2.2 動態(tài)屬性

與React類似, 直接對屬性對應的值進行{}賦值

<script>
    let src = '/tutorial/image.gif';
</script>
<img src={src}>

但與其他框架不同的是, svelte此時會提示:
A11y: <img> element should have an alt attribute
值得注意的是, 如果屬性名與變量名相同是, 可以采取省略簡寫:

<img {src} alt="A man dances.">

2.3 組件樣式

與Vue類似, 組件樣式直接在組件文件內(nèi)部進行定義

<p>This is a paragraph.</p>
<style>
    p {
        color: purple;
        font-family: 'Comic Sans MS', cursive;
        font-size: 2em;
    }
</style>

組件樣式默認包含Scope作用域

2.4 html賦值

跟Vue和React相同, 采用{var}進行視圖賦值, 無法直接寫入html內(nèi)容, 如果需要, 則需要使用 @html 修飾符

<p>{@html string}</p>

也可以在html元素中設置contenteditable="true", bind:innerHTML對HTML文本賦值

<div
    contenteditable="true"
    bind:innerHTML={html}
></div>

2.5 盒子元素屬性

每一元素都有clientWidth, clientHeight, offsetWidth, offsetHeight屬性,
每個屬性都是只讀的, 就算綁定的值發(fā)生變化, 對應的視圖也不會發(fā)生變化

2.6 元素引用

于Vue中的ref屬性類似,可以通過綁定this屬性來獲取綁定的對應元素

    <script>
        import { onMount } from 'svelte';
        let canvas;
        onMount(() => {
            // 此時canvas對象相當于 document.querySelector("canvas")的DOM元素
            const ctx = canvas.getContext('2d');
        }
    </script>
    <canvas
        bind:this={canvas}
        width={32}  
        height={32}
    ></canvas>

當綁定對象是一個組件時,獲取到的就是這一組件的實例

三. 事件

3.1 事件監(jiān)聽

在DOM元素上, on:event="{function}", eg:

<script>
    let count = 0;
    function incrementCount() {
     count += 1;
    }
</script>
<button on:click={incrementCount}>

3.2 派生屬性(計算屬性)

使用 $: var; 修飾, eg:

<script>
    let count = 0;
    $: doubled = count * 2;
</script>

此時計算屬性對應的變量也具有響應式渲染視圖的功能,但是該方法不止用定義響應數(shù)據(jù)的功能,也可實現(xiàn)類似watch屬性.
eg:

<script>
    let count = 0;
    $: {
        console.log('the count is ' + count);
        alert('I SAID THE COUNT IS ' + count);
    }
</script>

此時當count變量值發(fā)生變化時, 就是觸發(fā) $: 定義好方法執(zhí)行, 當然也可以通過if判斷來進行條件執(zhí)行

    $: if (count >= 10) {
        alert('count is dangerously high!');
        count = 9;
    }

注意: 響應式變量的更新只能通過給變量賦值來觸發(fā)更新。也就是說

    let obj = { a: 1, b: 2, c: 3, d: 4 };
    !func() {
      obj.a = 2;  
    }()
    <h2>obj.a是 {obj.a}</h2>

此時視圖中顯示的值還是1,即只修改對象內(nèi)部的值并不會觸發(fā)更改, 而且

    let obj = { a: 1, b: 2, c: 3, d: 4 };
    !func() {
      obj.a = 2;  
    }()
    obj = obj

這種賦值方式也是不行的

3.3 事件修飾符

如果只想監(jiān)聽一次DOM的點擊事件, 可以使用|修飾符 on:click|once={function}, 其他修飾符與此相同.

3.4 聲明周期

每個組件在使用時都會觸發(fā)聲明周期函數(shù)
生命周期函數(shù)需要從svelte中導入并調(diào)用

<script>
    import { onMount } from 'svelte';
</script>

常用的生命周期函數(shù):

  • onMount: 組件掛起時調(diào)用
  • onDestroy: 組件銷毀后調(diào)用
  • beforeUpdate: 組件數(shù)據(jù)更新前調(diào)用
  • afterUpdate: 組件數(shù)據(jù)更新后調(diào)用

與Vue相同, 組件內(nèi)部完成一次數(shù)據(jù)更新視圖的操作之后, 就會觸發(fā)一次tick函數(shù)
tick函數(shù)返回一個Promise對象

    <script>
        import { tick } from 'svelte';
        await tick();
        todo1();
        todo2();
    </script>

四. 組件通信

4.1 父組件向子組件傳參

父組件在引用子組件屬性中傳入?yún)?shù), 子組件用export關鍵字定義接收參數(shù)的變量
父組件:

    <script>
        import Nested from './Nested.svelte';
    </script>
    <Nested answer={42}/>

子組件:

    <script>
        export let answer;
    </script>
    <p>The answer is {answer}</p>

再次強調(diào), export區(qū)別于JS ES6在svelte中并不是導出的意思
我們也可以子組件接收數(shù)據(jù)的變量定義默認值

    export let answer = 2;

當需要傳入的參數(shù)過多時, 可以對象解構(gòu)的方式自動傳入
父組件

<script>
    import Info from './Info.svelte';

    const pkg = {
        name: 'svelte',
        version: 3,
        speed: 'blazing',
        website: 'https://svelte.dev'
    };
</script>

<Info {...pkg}/>

子組件也可以使用svelte內(nèi)置變量$$props直接從父組件傳入子組件的參數(shù)取值,而不用依次定義變量
子組件

<p>
    The <code>{$$props.name}</code> package is {$$props.speed} fast.
    Download version {$$props.version} from <a >npm</a>
    and <a href={$$props.website}>learn more here</a>
</p>

4.2 子組件向父組件傳參

需要使用DOM中的事件分發(fā)機制
子組件

<script>
    import { createEventDispatcher } from 'svelte';
    const dispatch = createEventDispatcher();
    function sayHello() {
        dispatch('message', {
            text: 'Hello!'
        });
    }
</script>

此時附件就可以接收到一個message事件
父組件

<Child on:message={handleMessage}/>

如果是多層嵌套組件的數(shù)據(jù)傳遞, 中間組件可以去除處理方法, 當然也可以重寫createEventDispatcher事件;

    <Child on:message />

同樣的, 如果父組件想監(jiān)聽子組件的事件, 也可以借用該機制
子組件分發(fā)點擊事件

<button on:click>
    Click me
</button>

父組件監(jiān)聽子組件的點擊事件

<script>
    import CustomButton from './CustomButton.svelte';
    function handleClick() {
        alert('Button Clicked');
    }
</script>
<CustomButton on:click={handleClick}/>

4.3 表單數(shù)據(jù)

使用 bind:value={var} 來對表單數(shù)據(jù)進行雙向綁定, 雖然說在DOM中一切都是String類型, 但var定義的類型并不局限于String
如果使用的變量名也為value, 則可以采用簡寫

<input bind:value></input>

部分表單組件并不是都跟value屬性進行雙向綁定, 這點跟Vue中的v-model有點區(qū)別
eg:

    <input type=radio bind:group={scoops} name="scoops" value={1}>
    <input type=checkbox bind:checked={yes}>

另外, 如果子父組件通信中剛好是用value屬性進行屬性傳遞也可以使用 bind:value 進行數(shù)據(jù)的雙向綁定,
但這種方式官方并不推薦, 因為這與 單一數(shù)據(jù)來源 這一設計理念相違背,數(shù)據(jù)的追蹤也不是很好處理

4.4 狀態(tài)管理

4.4.1 writable

store.js文件中定義一個狀態(tài)管理器

  import { writable } from 'svelte/store';
  export const count = writable(0);

注意, 一定要將狀態(tài)管理器導出
調(diào)用狀態(tài)管理器的update方法修改狀態(tài), 調(diào)用subscribe方法監(jiān)聽狀態(tài)變化

  // 1. 狀態(tài)修改
  count.update(n => n + 1);
  // 也可以調(diào)用set方法直接賦值
  count.set(0);
  // 2. 狀態(tài)變化監(jiān)聽
  count.subscribe(value => {
    console.log(value);
  });

注意, 上面的count不能直接作用于視圖, 需要用一個額外的變量托管其值

<script>
    import { count } from './stores.js';
    let countValue;
    count.subscribe(value => {
        countValue = value;
    });
</script>
<h1>The count is {countValue}</h1>

當前, 你也可以在頁面銷毀,取消對狀態(tài)管理的監(jiān)聽

    const unsubscribe = count.subscribe(value => {
        countValue = value;
    });
    onDestroy(unsubscribe);

當頁面中狀態(tài)管理過多時, svelte提供了$語法糖, 使其可以直接獲取當前狀態(tài)值(即: 自動監(jiān)聽 auto-subscribe)

    import { count } from './stores.js';
    <h1>The count is { $count }</h1>

任何以$為前綴的變量都會認為狀態(tài)變量, 因此svelte框架會試圖阻止你使用$前綴定義自己所屬變量.

處于自動監(jiān)聽的狀態(tài)對象, 可以直接對其賦值,即可實現(xiàn)狀態(tài)與視圖的雙向綁定

4.4.2 readable

并不是所有的狀態(tài)都是需要在組件中修改的,當你想定義一個狀態(tài)并且只想引用時或者其變量只能狀態(tài)內(nèi)部發(fā)生變化時, 我們可以使用readable函數(shù)定義狀態(tài)變量
store.js中:

export const time = readable(new Date(), function start(set) {
    const interval = setInterval(() => {
        set(new Date());
    }, 1000);

    return function stop() {
        clearInterval(interval);
    };
});

其中, 第一個參數(shù)初始化狀態(tài)值,可以未null或者undefined, 第二個參數(shù)定義start: Function函數(shù), 此函數(shù)在初次引用狀態(tài)時調(diào)用, 調(diào)用時傳入set方法用來修改變量值, 返回一個stop:Function函數(shù)
在取消狀態(tài)監(jiān)聽時, 會調(diào)用stop: Function函數(shù)

  import { onDestroy } from 'svelte';
  import { count } from './stores.js';
  let countValue;
  const unsubscribe = count.subscribe(value => {
    countValue = value;
  });
  onDestroy(unsubscribe);

在上述代碼中,每執(zhí)行一次subscribe方法, 在readable對象內(nèi)部就會執(zhí)行一次subscribes.add方法, 每次執(zhí)行一次unsubscribe方法, 就是執(zhí)行一次subscribes.delete方法.
subscribes是一個Map對象, 當執(zhí)行unsubscribe方法時subscribes的長度為空, 就會執(zhí)行stop方法, 因此如果一個組件多次實例并銷毀而沒有調(diào)用subscribe返回的unsubscribe取消監(jiān)聽的話, 有可能會造成內(nèi)存溢出, 因此當調(diào)用subscribe時需要記得在合適的時候取消監(jiān)聽

4.4.3 derived

如果定義一個狀態(tài)需要引入另外一個狀態(tài)的值, 則需要用derived函數(shù)定義狀態(tài), 如:

import { readable, derived } from 'svelte/store';
export const time = readable(new Date(), function start(set) {
  const interval = setInterval(() => {
    set(new Date());
  }, 1000);
  return function stop() {
    clearInterval(interval);
  }
})
export const elapsed = derived(
    time,
    $time => Math.round(($time - start) / 1000)
);

五. 模板渲染

5.1 if語句

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

一般常用, {# if condition} / {: else if condition} / {: else} / {/ if}

其中,

  • # 表示控制語句開始符
  • / 表示控制語句結(jié)束符
  • : 表示控制語句繼續(xù)表達

5.2 each語句 (循環(huán)渲染語句)

使用 {# each item as list, index} 來表達循環(huán)渲染, 其中l(wèi)ist必須為可迭代對象

    {#each cats as cat, i}
        <li data-id={cat.id}>
            {cat.name}
        </li>
    {/each}

可迭代對象可使用解構(gòu)方式表達

    {#each cats as {id, name}, i}
        <li data-id={cat.id}>
            {cat.name}
        </li>
    {/each}

當可迭代對象的長度發(fā)生更改時, 如果渲染的是組件, 可能會造成視圖渲染的錯誤/錯位, 這時就需要綁定渲染每個item的唯一值, 用(item.id)表示

<script>
    import Thing from './Thing.svelte';
    let things = [
        { id: 1, name: 'apple' },
        { id: 2, name: 'banana' },
        { id: 3, name: 'carrot' },
        { id: 4, name: 'doughnut' },
        { id: 5, name: 'egg' },
    ];
</script>
{#each things as thing (thing.id) }
    <Thing name={thing.name}/>
{/each}

5.3 await語句

當渲染的數(shù)據(jù)不是能實時獲取時, 可以用await語句等待數(shù)據(jù)返回后再進行渲染

<script>
    async function getRandomNumber() {
        const res = await fetch(`/tutorial/random-number`);
        const text = await res.text();

        if (res.ok) {
            return text;
        } else {
            throw new Error(text);
        }
    }
    let promise = getRandomNumber();
</script>

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

await 接收Promise對象
then 接收Promise處理成功之后的返回數(shù)據(jù)變量
catch 接收Promise處理失敗之后返回錯誤對象

六. 內(nèi)置組件

svelte提供一些內(nèi)置組件, 默認全局注冊,無需額外導入使用

6.1 self

正常情況下, 組件本身無法引用自身組件

例如, 在Folder.svelte組件中

{#if file.files}
    <Folder {...file}/>
{:else}
    <File {...file}/>
{/if}

上述代碼是無法實現(xiàn)的, 因為Folder組件本身無法使用Folder組件, 到可以使用svelte的內(nèi)置self改造實現(xiàn),

{#if file.files}
    <svelte:self {...file}/>
{:else}
    <File {...file}/>
{/if}

6.2 component

動態(tài)組件, 類似于Vue中的is=component

例如,不同的條件下渲染不同組件,常規(guī)方式下

<script>
    const options = [
        { color: 'red',   component: RedThing   },
        { color: 'green', component: GreenThing },
        { color: 'blue',  component: BlueThing  },
    ];

    let selected = options[0];
</script>
{#if selected.color === 'red'}
    <RedThing />
{:else if selected.color === 'green'}
    <GreenThing />
{:else if selected.color === 'blue'}
    <BlueThing />
{/if}

使用動態(tài)組件可以進行簡化

<svelte:component this={selected.component}/>

其中html標簽也可以視為一個組件

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容