Svelte前端框架JavaScript響應式系統Runes2026

Svelte 5 Runes 實戰:告別複雜的 Dependency Array,迎接細粒度反應式

Svelte 5 Runes 完整教學!深入解析 $state、$derived、$effect 與 React/Vue 的差異。附完整程式碼範例,工程師必看的響應式系統新選擇!

· 7 分鐘閱讀

React 的 useEffect 一直被抱怨「太複雜」——dependency array 稍有不慎就會造成 stale closure,各種 lint 規則讓人疲於奔命。Vue 的 refreactive 簡單很多,但響應式系統藏在 runtime 裡,有隱性開銷。Svelte 5 的 Runes,則是另一條路——編譯器幫你處理一切,API 表面簡單,實際上是用編譯時的最佳化,換取 runtime 的效能。


前端工程師為什麼要關心 Svelte 5

Svelte 這個框架在過去幾年一直處於「大家都知道它快,但很少人真的在用」的狀態。2024 年 Svelte 5 發布、Runes 系統正式登場之後,這個情況開始改變了。

Runes 不只是新語法,而是 Svelte 對「響應式」這個問題的重新思考。它借鑒了 Signals(信號)的概念,讓狀態追蹤在編譯時就完成,而不是在 runtime 時靠 Proxy 或 Observable 實現。這讓 Svelte 5 的效能領先多數框架:根據 benchmark,在復雜的響應式更新場景下,Svelte 5 可達到每秒 39.5 萬次操作。

如果你對現有的響應式方案(React 的 useEffect、Vue 的 watchEffect、MobX 的 autorun)感到疲憊,或者你在選下一個專案的框架,Runes 值得你認真評估。


Runes 是什麼:編譯時的響應式魔法

Runes(符文)是 Svelte 5 引入的編譯指令,以 $ 前綴標示。它們會在編譯階段被分析,建立起精确的依賴追蹤圖(dependency graph),runtime 時只需要執行這個已經最佳化的圖。

這跟 React 的做法有根本差異:

  • React:runtime 時靠 Hooks 的 caller cache + dependency array 來猜測依賴,compiler 是可選的(React Compiler)
  • Svelte:整個 framework 是 compiler-first,dependency graph 在 build time 就已經確定,runtime 只是執行確定了的指令

Runes 一覽表

Rune功能類比
$state反應式狀態React: useState
$derived計算值(自動快取)React: useMemo
$effect副作用(自動 cleanup)React: useEffect
$props元件屬性(输入)React: props
$bindable雙向綁定Vue: v-model

$state:最基礎的響應式狀態

基本用法

<script>
  // 簡單的計數器
  let count = $state(0);
  // count 是一個信號,修改它會觸發所有依賴它的地方更新

  function increment() {
    count += 1;  // 直接賦值,Svelte 編譯器會自動包裝成 .value 的存取
  }

  function decrement() {
    count -= 1;
  }
</script>

<button onclick={decrement}>-</button>
<span>{count}</span>
<button onclick={increment}>+</button>

物件與陣列

<script>
  // 淺層響應式(預設)
  let user = $state({ name: '小明', age: 28 });

  // 深層響應式:當物件內任意屬性變化時,整個 user 被視為 dirty
  let settings = $state({
    theme: 'dark',
    notifications: true,
    language: 'zh-TW'
  });

  function updateTheme() {
    settings.theme = settings.theme === 'dark' ? 'light' : 'dark';
  }
</script>

<!-- settings.theme 變化時,這一行會更新 -->
<p>Current theme: {settings.theme}</p>
<button onclick={updateTheme}>Toggle Theme</button>

$state 和普通变量的区别

<script>
  let normalVar = 0;       // ❌ 不是響應式,變化不會觸發 UI 更新
  let reactiveState = $state(0);  // ✅ 是響應式
</script>

$derived:不再手寫 Dependency Array

基本用法

$derived 讓你用宣告式的方式宣告一個計算值——不需要 dependency array,不需要 useMemo,編譯器自動幫你建立依賴圖。

<script>
  let price = $state(100);
  let quantity = $state(2);
  let discount = $state(0.1); // 10% 折扣

  // ✅ derived:只要 price/quantity/discount 任一個變化,total 自動重新計算
  let total = $derived(price * quantity * (1 - discount));

  // ✅ derived 也可以用表達式描述複雜邏輯
  let formattedTotal = $derived(
    new Intl.NumberFormat('zh-TW', {
      style: 'currency',
      currency: 'TWD'
    }).format(total)
  );
</script>

<p>總計:{formattedTotal}</p>

巢狀 Derived:鏈式計算

<script>
  let items = $state([
    { name: '鍵盤', price: 1500, qty: 1 },
    { name: '滑鼠', price: 800, qty: 2 },
    { name: '螢幕', price: 8000, qty: 1 },
  ]);

  // 鏈式 derived
  let subtotals = $derived(items.map(item => item.price * item.qty));
  let totalAmount = $derived(subtotals.reduce((sum, val) => sum + val, 0));
  let isExpensive = $derived(totalAmount > 10000);  // derived 也可以 derived
  let message = $derived(
    isExpensive ? `訂單金額 ${totalAmount},已超過万元大關!` : `訂單金額 ${totalAmount}`
  );
</script>

<p>{message}</p>

$derived vs $derived.by

當計算邏輯複雜時,用 $derived.by 寫成函式:

<script>
  let items = $state([3, 1, 4, 1, 5, 9, 2, 6]);

  // 簡單表達式 → $derived
  let count = $derived(items.length);

  // 複雜邏輯 → $derived.by
  let stats = $derived.by(() => {
    const sorted = [...items].sort((a, b) => a - b);
    const sum = items.reduce((a, b) => a + b, 0);
    const mean = sum / items.length;
    const median = sorted.length % 2 === 0
      ? (sorted[sorted.length / 2 - 1] + sorted[sorted.length / 2]) / 2
      : sorted[Math.floor(sorted.length / 2)];
    return { sum, mean, median, min: sorted[0], max: sorted[sorted.length - 1] };
  });
</script>

<p>Sum: {stats.sum}, Mean: {stats.mean.toFixed(2)}, Median: {stats.median}</p>

$effect:自動 Cleanup 的副作用

基本用法

$effect 是 Svelte 5 的響應式副作用,自動追踪依賴,自動 cleanup。

<script>
  let query = $state('');
  let results = $state([]);

  // ✅ 當 query 變化時,自動重新執行
  // 組件卸載時,自動執行 cleanup 函式
  $effect(() => {
    if (!query) {
      results = [];
      return;
    }

    const controller = new AbortController();

    fetch(`/api/search?q=${encodeURIComponent(query)}`, {
      signal: controller.signal
    })
      .then(res => res.json())
      .then(data => { results = data; })
      .catch(err => {
        if (err.name !== 'AbortError') console.error(err);
      });

    // ✅ cleanup 函式:下次 effect 執行前 或 組件卸載時 自動呼叫
    return () => {
      controller.abort();
    };
  });
</script>

<input bind:value={query} placeholder="搜尋..." />
{#each results as result}
  <p>{result.name}</p>
{/each}

$effect 的 dependency 自動追蹤

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

  // ✅ 這個 effect 只追蹤 theme 的變化,count 改變不會觸發它
  $effect(() => {
    document.body.dataset.theme = theme;
    // 你不需要寫 dependency array
    // Svelte 自動知道這個 effect 只依賴 theme
  });
</script>

React useEffect 的常見痛苦 vs Svelte $effect

// React:dependency array 的痛苦
useEffect(() => {
  // 這個 callback 每次渲染都創建新的
  // 裡面的變量可能是 stale
  const id = setInterval(() => {
    setCount(c => c + 1); // 閉包裡的 count 可能是舊值
  }, 1000);

  return () => clearInterval(id);
  // ESLint 會一直提醒你:count or theme 在 dependency array 裡嗎?
}, [count, theme]); // ← 這裡忘記加任何一個,都可能造成 bug
// Svelte:不需要 dependency array
<script>
  import { onDestroy } from 'svelte';

  let count = $state(0);
  let theme = $state('dark');

  $effect(() => {
    // 這個 effect 只在 theme 變化時執行
    // 不需要 dependency array,compiler 幫你分析
    document.body.dataset.theme = theme;
  });

  // setInterval 當然也可以放在 $effect 裡
  $effect(() => {
    const id = setInterval(() => {
      count += 1; // 這個 count 是 reactive 的,Svelte 保證是最新值
    }, 1000);

    onDestroy(() => clearInterval(id)); // 或者用 onDestroy
    // 也可以 return cleanup 函式,兩者等效
  });
</script>

$props 與 $bindable:元件的輸入輸出

$props:宣告式接收屬性

<script>
  // ✅ $props() 是 reactive 的,解構出來的都是 reactive
  let { name, age = 18, onClick } = $props();

  // 也可以用 $derived 從 props 衍生值
  let isAdult = $derived(age >= 20);
</script>

<button onclick={onClick}>
  {name} ({isAdult ? '成年' : '未成年'})
</button>

$bindable:雙向綁定

當你需要讓父元件可以直接雙向綁定子元件的狀態時,使用 $bindable

<!-- Toggle.svelte -->
<script>
  let { value = $bindable(false), label = '' } = $props();
</script>

<label>
  <input type="checkbox" bind:checked={value} />
  {label}
</label>
<!-- Parent.svelte -->
<script>
  import Toggle from './Toggle.svelte';

  let isEnabled = $state(false);
</script>

<!-- ✅ bind: 雙向綁定,isEnabled 跟 Toggle 內部的 value 同步 -->
<Toggle bind:value={isEnabled} label="啟用功能" />
<p>目前狀態:{isEnabled ? '啟用' : '停用'}</p>

Props 解構的 Default Values

<script>
  let {
    // 必填屬性
    title,
    // 有預設值的屬性
    count = 0,
    // 函式屬性(事件處理)
    onUpdate = () => {},
    // rest props
    ...rest
  } = $props();
</script>

Runes 生態:從 React/Vue 遷移的實務觀點

從 React 遷移

React 概念Svelte 5 Runes 對應
useState$state
useMemo$derived$derived.by
useEffect$effect
useCallback直接用 function(Svelte 不需要)
受控元件$bindable
ContextSvelte context API(不是 Runes)

從 Vue 遷移

Vue 概念Svelte 5 Runes 對應
ref$state
computed$derived
watch / watchEffect$effect
v-model$bindable
defineProps$props

Runes 的效能優越性

Svelte 5 的編譯器將 reactive 語句直接編譯成 vanilla JavaScript DOM 操作,沒有 Virtual DOM 這個中間層:

// 這個 Svelte 程式碼
<script>
  let count = $state(0);
</script>
<button onclick={() => count++}>{count}</button>

// 編譯後大概像這樣(簡化):
let count = 0;
const btn = document.querySelector('button');
const span = document.querySelector('span');
btn.addEventListener('click', () => {
  count++;
  span.textContent = count; // 直接操作 DOM,沒有 VDOM diffing
});

這就是為什麼 Svelte 在 benchmark 裡領先——它沒有 React 的 reconciliation 開銷,也沒有 Vue 的 Proxy 攔截 overhead。


結語:Runes 適合你嗎?

Runes 不是銀彈,但它是近年來最有新意的響應式系統設計之一。

Runes 的優點

  • 編譯器處理依賴追蹤,開發者不需要操心 dependency array
  • $effect 的自動 cleanup 機制大幅減少記憶體洩漏風險
  • 沒有 Virtual DOM,直接操作 DOM,效能優秀
  • API 表面簡單,但底層概念嚴謹

Runes 的缺點

  • 學習曲線:Signal 模型對 React 開發者來說需要心態調整
  • 生態系小:社群資源、第三方庫落後 React/Vue
  • 團隊熟悉度:多數前端團隊對 React 經驗更深
  • $state 的深層響應式需要理解 compiler 的行為

如果你在評估新框架、或你對效能非常敏感,Runes 值得你花週末下午認真研究一下。


延伸閱讀


本文是「2026 前端框架選擇」系列文章之一。如果你有興趣看「Svelte 5 遷移實戰」或「Svelte vs React Compiler」的深入比較,歡迎留言。