Svelte 使用 Actions

tags: Svelte
category: Front-End
description: Svelte 使用 Actions
created_at: 2023/04/18 12:00:00

cover image


回到 手把手開始寫 Svelte


前言

這個我覺得比較像 Vue 3 的指令(directive),都是綁在 DOM 上面,然後可以根據生命週期去做事。

然後剛剛回去看發現我的 Vue 3 好像沒寫到指令

總之這篇就會以我之前練比賽的時候做的指令當範例,轉換成 Svelte 的版本。

是一個子功能,當初把他抽出來當指令,因為覺得程式碼會變得更漂亮


基本用法

語法是 use:xxx,然後那個 xxx 是一個函數,先看看函數定義:

action = (node: HTMLElement, parameters: any) => {
    update?: (parameters: any) => void,
    destroy?: () => void
}

然後官方也有提供淺顯易懂的範例:

<script>
    export let bar;

    function foo(node, bar) {
        // the node has been mounted in the DOM

        return {
            update(bar) {
                // the value of `bar` has changed
            },

            destroy() {
                // the node has been removed from the DOM
            }
        };
    }
</script>

<div use:foo={bar}></div>

簡單來說就是 node 會吃到你綁定的元素,而 parameters 則是上例的 bar,然後這個函數會在 mounted 被觸發,也可以回傳兩個函數:

  • update: 當 parameters 有變更的時候
  • destroy: 當 nodedestroy 的時候觸發

再來看基礎範例:

<script lang="ts">
    function hello(node: HTMLElement) {
        console.log(node);
    }
</script>

<div use:hello>Hello World</div>

這樣就會在 console 看到那個 div 被印出來,再來你就可以做一堆處理了。

例如你也可以為他監聽事件:

function hello(node: HTMLElement) {
    node.addEventListener('click', () => {
        console.log('click');
    });
}

也可以把事件綁在 document 身上,不過如果綁在 node 以外,要記得一定要回傳 destroy 的時候做一些清理(例如刪除事件)。

如果綁在 node 上的話也可以養成好習慣做清理,如果你想照顧比較舊的瀏覽器的話,因為他可能沒有處理好而一樣導致memory leak


範例

因為我一時想不到其他簡單的Demo範例,所以直接來看實際案例吧

需求是這樣子的,我希望透過 use:touch,可以讓元素在電腦版可以像手機上一樣透過點擊然後動滑鼠來做捲動。

所以首先先來做一塊區域:

<div style="width: 200px; height: 200px; overflow: scroll;">
    <ul>
        <li>1</li>
        <li>2</li>
        <li>3</li>
        <li>4</li>
        <li>5</li>
        <li>6</li>
        <li>7</li>
        <li>8</li>
        <li>9</li>
        <li>10</li>
        <li>11</li>
        <li>12</li>
        <li>13</li>
        <li>14</li>
        <li>15</li>
        <li>16</li>
        <li>17</li>
        <li>18</li>
        <li>19</li>
        <li>20</li>
    </ul>
</div>

然後宣告 touchaction 出來用:

    function touch(node: HTMLElement) {
        node.dataset.touch = 'false';

        const mousedown = () => {
            node.dataset.touch = 'true';
        };

        const mousemove = (e: MouseEvent) => {
            if (node.dataset.touch === 'true') {
                node.scrollTop -= e.movementY;
                node.scrollLeft -= e.movementX;
            }
        };

        const mouseup = () => {
            node.dataset.touch = 'false';
        };

        node.addEventListener('mousedown', mousedown);
        node.addEventListener('mousemove', mousemove);
        window.addEventListener('mouseup', mouseup);

        return {
            destroy() {
                node.removeEventListener('mousedown', mousedown);
                node.removeEventListener('mousemove', mousemove);
                window.removeEventListener('mouseup', mouseup);
            }
        };
    }

最後記得套用到 div 身上:

<div style="width: 200px; height: 200px; overflow: scroll;" use:touch>
    <!-- 略... -->
</div>

這時就可以透過滑鼠點擊然後上下滑動來捲動了。


有參數的範例

這邊臨時想到的範例是之前寫測試的時候會用到 test-id 這個屬性,但又不希望 production 出去的時候暴露出來讓爬蟲太好抓(雖然好像也有現成的套件可以幫忙拿掉),所以做了一個指令,只在 developmenttesting 狀態(總之就是非 production)的時候才產生 test-id 來用。

這個需求反而比上面的還單純:

    function testid(node: HTMLElement, testid: string) {
        if (import.meta.env.MODE !== 'production') {
            node.setAttribute('data-testid', testid);
        }
    }

然後用起來也很簡單:

<div use:testid={'hi'}>Hi</div>

然後就可以自己執行跟 Build 看看,甚至可以寫測試看看,都會是正常的。


總結

這個功能相對簡單很多,但是也很有彈性可以做更多事情,總之 use:xxx 就是會去吃 xxx 這個函數,然後會得到 node 跟後面傳入的 params,這也非常的直覺。




最後更新時間: 2023年04月18日.