Svelte 5 Runes 實戰:從 $state、$derived、$effect 到 Svelte 4 遷移

前端工程

Svelte 5 為什麼值得關注

Svelte 5 用 Runes(符文) 重寫了響應式系統,從「編譯期依賴追蹤」升級為「顯式、細粒度、跨檔案」的響應式原語。它解決了 Svelte 4 時代 let 變數在閉包/非同步/跨模組場景下的追蹤失效問題,同時帶來更小的執行期與更高的效能。

維度 Svelte 4 Svelte 5 (Runes)
狀態宣告 let count = 0 let count = $state(0)
派生值 $: doubled = count * 2 let doubled = $derived(count * 2)
副作用 $: console.log(count) $effect(() => console.log(count))
跨模組共享 需 stores 可在 .svelte.js 直接用 Runes
元件 Props export let let { x } = $props()

核心 Runes 一覽

$state:宣告響應式狀態

<script>
  let count = $state(0);
  let user = $state({ name: "Ada", age: 36 });

  function inc() {
    count++;                 // 自動觸發更新
    user.age++;              // 深層屬性同樣響應式(代理實作)
  }
</script>

<button onclick={inc}>{count} - {user.age}</button>

注意:$state 對物件和陣列使用 深度代理(deep proxy),所以 user.age++ 也能精準追蹤,不再需要 Svelte 4 的「整體替換」寫法。

$derived:宣告派生值

<script>
  let price = $state(100);
  let qty = $state(2);
  let total = $derived(price * qty);     // 僅在依賴變化時重算
  let label = $derived.by(() => {
    // 複雜邏輯用 .by 包裹函式
    return price > 0 ? `¥${price * qty}` : "無效價格";
  });
</script>

<p>合計:{total} / {label}</p>

$effect:宣告副作用

<script>
  let count = $state(0);
  $effect(() => {
    // 讀取 count 即建立依賴,count 變化重新執行
    document.title = `計數:${count}`;
  });
</script>

$effect 只在瀏覽器執行,適合 DOM 同步、第三方函式庫綁定、訂閱清理等。需要清理時用回傳函式:

<script>
  let el = $state();
  $effect(() => {
    if (!el) return;
    const obs = new ResizeObserver(() => {/* ... */});
    obs.observe(el);
    return () => obs.disconnect();   // 自動清理
  });
</script>

<div bind:this={el}></div>

Props 與雙向綁定

$props 替代 export let

<!-- Child.svelte -->
<script>
  let { title, count = 0, onchange } = $props();
</script>

<h2>{title}: {count}</h2>

$bindable 實現可綁定 Props

<!-- Child.svelte -->
<script>
  let { value = $bindable("") } = $props();
</script>

<input bind:value />

<!-- Parent.svelte -->
<script>
  let name = $state("");
</script>

<Child bind:value={name} />
<p>父元件:{name}</p>

.svelte.js 模組裡複用 Runes

Svelte 5 的重大進步:Runes 不再侷限於 .svelte,可在 .svelte.js / .svelte.ts 中宣告響應式狀態,實現真正的跨元件狀態複用。

// counter.svelte.js
export const createCounter = () => {
  let count = $state(0);
  const inc = () => count++;
  const double = $derived(count * 2);
  return { get count() { return count; }, get double() { return double; }, inc };
};
<script>
  import { createCounter } from "./counter.svelte.js";
  const c = createCounter();
</script>

<button onclick={c.inc}>{c.count} / {c.double}</button>

當元件狀態涉及複雜配置(如主題色、對比度閾值),可先用 JSON 格式化 工具組織好配置物件,再用 Runes 包裝成響應式 store。


生命週期的變化

Svelte 4 Svelte 5 說明
onMount onMount 仍可用 僅掛載時一次
onDestroy $effect 回傳清理函式 替代銷毀清理
$: 副作用 $effect 響應式副作用統一入口
beforeUpdate/afterUpdate $effect.pre / $effect 更細粒度控制
<script>
  import { onMount } from "svelte";
  let ready = $state(false);
  onMount(() => { ready = true; });   // 掛載邏輯仍可用
</script>

從 Svelte 4 平滑遷移

Svelte 5 相容大部分 Svelte 4 語法(legacy 模式),但建議按步驟遷移:

  1. 升級依賴npm i svelte@5,遷移工具 npx sv migrate svelte-5
  2. 先遷狀態:把 let x = 顯性改為 $state(),尤其跨閉包/非同步的變數。
  3. 再遷派生$: y = f(x)let y = $derived(f(x))
  4. 再遷副作用$: sideEffect(x)$effect(() => sideEffect(x))
  5. 最後遷 Propsexport let$props(),需要雙向的加 $bindable
<!-- 遷移前 (Svelte 4) -->
<script>
  export let value = 0;
  $: doubled = value * 2;
  $: console.log(doubled);
</script>

<!-- 遷移後 (Svelte 5) -->
<script>
  let { value = 0 } = $props();
  let doubled = $derived(value * 2);
  $effect(() => console.log(doubled));
</script>

涉及顏色、對比度等視覺配置時,可借助 顏色轉換顏色對比度 工具驗證主題可存取性(WCAG)。


常見問題 FAQ

Q1:Runes 能在普通 .js 檔用嗎?

不能。必須在 .svelte.svelte.js.svelte.ts 檔案中使用,編譯器才能識別。

Q2:$effect 裡的非響應式變數變化會觸發嗎?

不會。$effect 只追蹤執行期間被讀取的響應式狀態。讀取普通變數不會建立依賴。

Q3:Svelte 4 程式碼能直接跑在 Svelte 5 嗎?

大部分可以(legacy 相容)。但混用 Runes 與舊語法時,同一元件內不能混,建議按元件逐步遷移。

Q4:$derived 和 $effect 怎麼選?

派生「值」用 $derived;「做事情」(DOM、訂閱、日誌)用 $effect

Q5:效能提升來自哪裡?

細粒度追蹤 + 編譯期最佳化,避免了虛擬 DOM 的 diff 開銷,狀態更新直接命中最小 DOM 節點。


工具推薦

在 Svelte 5 開發中,以下 工具庫 能幫上忙:


Svelte 5 的 Runes 不是語法糖,而是響應式心智模型的升級:狀態顯式、依賴精確、跨檔案複用。掌握 $state / $derived / $effect / $props,你就握住了下一代 Svelte 的鑰匙。

本站提供瀏覽器本地工具,免註冊即可試用 →

#Svelte#Svelte 5#Runes#前端框架#响应式#前端