Svelte 5 的即時反應 (Reactivity)

Svelte 5 引入了 runes 來控制即時反應,使其更加明確和強大。核心概念是使用 $state 來宣告反應性狀態。

使用 $state 的基本即時反應

要創建一個反應性變數,你可以使用 $state 來初始化它。對此變數的任何更改都將自動觸發 DOM 的更新。

<script>
    let count = $state(0);

    function increment() {
        count += 1;
    }
</script>

<button onclick={increment}>
    Clicked {count}
    {count === 1 ? "time" : "times"}
</button>

在 Svelte 5 中,即時反應不再是基於賦值 (assignment)。相反地,$state 創建了一個 Svelte 會追蹤的訊號 (Signal)。當訊號的值發生變化時,Svelte 就會知道要更新什麼。

如果不使用 $state 的話,我們對 count 的任何更改都不會自動觸發 DOM 的更新。

<script>
// 如果不使用 `$state`,則 `count` 不是反應性的
let count = 0;

// ...
</script>

<!-- 點擊按鈕將不再更新 DOM,因為 `count` 不是 `$state` 創建的訊號 -->
<button onclick={increment}>
    Clicked {count}
    <!-- ... -->
</button>

常見錯誤:只引用 $state 的初始值

如果把 $state 的值直接指定給另一個變數,另一個變數只會取得當下的初始值。之後即使 $state 更新了,這個變數也不會跟著更新,因此 Svelte 會顯示以下錯誤:

Svelte: This reference only captures the initial value of userName. Did you mean to reference it inside a derived instead?

例如,下面的 greeting 只會是 訪客,不會隨著 userName 的變化而更新:

<script>
    let userName = $state("訪客");
    let greeting = userName;

    function updateUserName() {
        userName = "小明";
    }
</script>

<button onclick={updateUserName}>更新使用者名稱</button>
<p>{greeting}</p>

如果希望 greeting 始終反映 userName 的最新值,請使用 $derived

<script>
    let userName = $state("訪客");
    let greeting = $derived(userName);

    function updateUserName() {
        userName = "小明";
    }
</script>

<button onclick={updateUserName}>更新使用者名稱</button>
<p>{greeting}</p>

詳細說明請參考:state_referenced_locally

使用 $derived 的衍生狀態

有時候,你會希望某個狀態是依賴於其他狀態計算而來的。你可以使用 $derived 來創建一個計算值,當其依賴項發生變化時,它會自動重新計算。

<script>
    let count = $state(0);
    let doubled = $derived(count * 2);
</script>

<button onclick={() => count++}>
    Count: {count}
</button>

<p>{count} doubled is {doubled}</p>

使用 $effect 來根據狀態變化執行程式碼

要在狀態變化時執行某些程式碼,你可以使用 $effect。這對於日誌記錄、數據獲取或與瀏覽器 API 互動等操作非常有用。當內部的反應性值發生變化時,$effect 裡的程式碼將會重新執行。

<script>
    let count = $state(0);

    $effect(() => {
        // 每當 'count' 變化時,這段程式碼就會執行
        console.log(`The count is now ${count}`);

        if (count > 10) {
            alert(`Count is dangerously high!`);
        }
    });
</script>

<button onclick={() => count++}>
    Count: {count}
</button>

更新陣列和物件

有了 Svelte 5 的 runes,更新陣列和物件變得更加直觀。因為 $state 創建了深層的、具反應性的物件,所以你可以直接使用標準的變異方法,如 .push().pop().splice() 等,即時反應將如預期般運作。

你不再需要重新賦值變數來觸發更新。

<script>
    let numbers = $state([1, 2, 3]);

    function addNumber() {
        // 這在 Svelte 5 中會觸發即時反應
        numbers.push(numbers.length + 1);
    }

    function removeLast() {
        // 這也同樣有效
        numbers.pop();
    }
</script>

<button onclick={addNumber}> Add a number </button>

<button onclick={removeLast}> Remove last number </button>

<p>Numbers: {numbers.join(", ")}</p>

與舊版 Svelte 相比,這大大簡化了狀態管理。