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 模式),但建議按步驟遷移:
- 升級依賴:
npm i svelte@5,遷移工具npx sv migrate svelte-5。 - 先遷狀態:把
let x =顯性改為$state(),尤其跨閉包/非同步的變數。 - 再遷派生:
$: y = f(x)→let y = $derived(f(x))。 - 再遷副作用:
$: sideEffect(x)→$effect(() => sideEffect(x))。 - 最後遷 Props:
export 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>
常見問題 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 的鑰匙。
本站提供瀏覽器本地工具,免註冊即可試用 →