Skip to content

Svelte 5 Runes:響應式程式設計的範式轉變

Svelte 5 在 2024 年 10 月正式釋出,最核心的變化是引入了 Runes 系統。這是一個對響應式程式設計模型的根本性重構。從架構師視角分析一下這個變化的意義。

從隱式到顯式

Svelte 4 的響應式是隱式的——賦值即觸發更新:

svelte
<!-- Svelte 4 -->
<script>
  let count = 0;

  function increment() {
    count += 1; // 隱式觸發更新
  }
</script>

<button on:click={increment}>{count}</button>

這種方式簡潔,但在複雜場景下有侷限:跨檔案共享狀態、派生計算、副作用管理都需要額外的機制(stores、$ 語法糖)。

Svelte 5 引入 Runes,響應式變為顯式宣告:

svelte
<!-- Svelte 5 -->
<script>
  let count = $state(0);

  function increment() {
    count += 1; // 仍然是響應式的,但明確通過 $state 宣告
  }
</script>

<button onclick={increment}>{count}</button>

核心 Runes

$state — 響應式狀態

typescript
// 簡單值
let name = $state("張三");

// 物件自動深度響應
let user = $state({
  name: "張三",
  preferences: { theme: "dark" },
});

// 修改巢狀屬性也自動響應
user.preferences.theme = "light";

$derived — 派生值

替代 Svelte 4 的 $: 響應式宣告:

typescript
// Svelte 4
$: doubled = count * 2;
$: fullName = `${firstName} ${lastName}`;

// Svelte 5
const doubled = $derived(count * 2);
const fullName = $derived(`${firstName} ${lastName}`);

// 複雜派生
const stats = $derived.by(() => {
  const active = users.filter((u) => u.isActive);
  return {
    total: active.length,
    avgScore: active.reduce((s, u) => s + u.score, 0) / active.length,
  };
});

$effect — 副作用

typescript
// 替代 $: 和 onMount 的副作用
$effect(() => {
  // 訪問 count 時自動建立依賴
  console.log(`count is ${count}`);

  // 返回清理函式
  return () => {
    console.log("cleanup");
  };
});

// 只執行一次(類似 onMount)
$effect(() => {
  initAnalytics();
  return () => cleanupAnalytics();
}, []);

$props — 元件屬性

typescript
// Svelte 4
export let name = "default";
export let count;

// Svelte 5
let { name = "default", count, onsubmit }: Props = $props();

// 解構 + 重新命名
let { items: list = [] }: { items: string[] } = $props();

跨元件狀態共享

不再需要單獨的 store 模組:

typescript
// shared-state.ts
export function createCounter(initial = 0) {
  let count = $state(initial);

  return {
    get count() {
      return count;
    },
    increment() {
      count++;
    },
    decrement() {
      count--;
    },
  };
}

// 元件中使用
const counter = createCounter();
svelte
<script>
  import { createCounter } from "./shared-state";

  const counter = createCounter();
</script>

<button onclick={() => counter.increment()}>
  {counter.count}
</button>

和其他框架的對比

React:  useState/useMemo/useCallback — 函式式 + hooks 規則
Vue:    ref/computed/watch — 組合式 API
Svelte: $state/$derived/$effect — Runes 系統

Runes 的優勢在於:沒有 hooks 的規則限制(不能在條件語句中使用),沒有 ref 的 .value 煩惱。

TypeScript 整合

typescript
// 型別定義
interface User {
  name: string;
  email: string;
  role: "admin" | "user";
}

// $state 自動推斷型別
let user = $state<User>({
  name: "張三",
  email: "zhang@example.com",
  role: "admin",
});

// $props 型別
interface Props {
  title: string;
  items: User[];
  onSelect: (user: User) => void;
}

let { title, items, onSelect }: Props = $props();

遷移建議

Svelte 5 完全向後相容 Svelte 4 語法,可以漸進遷移:

  1. 新元件用 Runes 寫法
  2. 簡單元件逐步改寫($state, $derived)
  3. store 邏輯遷移到 $state 函式
  4. 最後處理複雜的 $: 語句

小結

  • Runes 讓 Svelte 的響應式從隱式變為顯式,可預測性更好
  • $state / $derived / $effect 覆蓋狀態、派生、副作用三大場景
  • 跨元件狀態共享不再需要單獨的 store 機制
  • 完全向後相容,支援漸進遷移
  • TypeScript 整合度高,型別推斷自然

MIT Licensed