$state と $derived: Svelte 5 Runes によるユニバーサルな反応性
応答性システムは、最新の UI フレームワークの中心です。 Svelte 5 は完全に再設計されました
反応性がどのように機能するか、Svelte 4 の「魔法の割り当て」から ルーン文字: 関数
プレフィックス付きの特殊な $ コンパイラと直接通信します。 2つの基本的なルーン
私は $state e $derived、これは、応答性のユースケースの 90% をカバーします。
あらゆる Svelte アプリケーション。
このガイドでは、これら 2 つのルーンが内部でどのように動作するのか、なぜ ES6 プロキシを使用するのかについて詳しく説明します。
深い反応性、 $derived 自動メモ化を実装します。さらに、
重要 — 通常の TypeScript モジュールの .svelte ファイルの外でどのように使用できるか、
まったく新しい状態管理パターン。
前提条件
- Svelte 5 の基礎知識 (記事 1: コンパイラー駆動のアプローチを参照)
- 中間 TypeScript (ジェネリック、プロキシ、ゲッター/セッター)
- メモ化と依存関係の追跡の概念
$state: ES6 プロキシを使用したリアクティブ状態
Svelte 4 では、ブロック内で宣言された変数 <script> それは自動的だった
リアクティブ: コンパイラは各割り当てを追跡し、更新コードを生成しました。これは役に立ちました
プリミティブ型ですが、オブジェクトと配列には制限がありました。次のような「深い」変更があります。
array.push() o obj.nested.prop = value 彼らは追跡されませんでした。
Svelte 5 はこれを次のように解決します。 $state、彼が使っている ES6プロキシ あらゆるものを傍受する
オブジェクトのアクセスと変更 (ネストされたものを含む)。その結果、真の深い反応性が得られます。
<script lang="ts">
// Tipi primitivi: reattivita diretta
let count = $state(0);
let name = $state('Federico');
let isVisible = $state(true);
// Oggetti: reattivita profonda tramite Proxy
let user = $state({
name: 'Federico',
address: {
city: 'Bari',
country: 'IT'
},
tags: ['developer', 'writer']
});
function updateCity() {
// Questo funziona! La modifica profonda e tracciata
user.address.city = 'Milano';
}
function addTag(tag: string) {
// Anche push() e tracciato grazie al Proxy
user.tags.push(tag);
}
</script>
<p>Citta: {user.address.city}</p>
<p>Tags: {user.tags.join(', ')}</p>
Svelte 4 では、 user.address.city = 'Milano' UI の更新はトリガーされませんでした
コンパイラは最上位変数への代入のみを描画したためです (user = ...)。
Svelte 5 とプロキシを使用すると、あらゆる深さレベルのアクセスが傍受されます。
$state プロキシが内部的にどのように動作するか
深い応答性を理解するには、ES6 プロキシがどのように機能し、Svelte がそれをどのように使用するかを理解する必要があります。
// Implementazione concettuale del Proxy usato da $state
// (semplificata rispetto al codice reale di Svelte 5)
function createReactiveProxy<T extends object>(
target: T,
onChange: () => void
): T {
return new Proxy(target, {
get(obj, prop) {
const value = Reflect.get(obj, prop);
// Se il valore e un oggetto/array, crea un Proxy annidato
if (value !== null && typeof value === 'object') {
return createReactiveProxy(value, onChange);
}
return value;
},
set(obj, prop, value) {
const result = Reflect.set(obj, prop, value);
// Notifica gli osservatori che qualcosa e cambiato
onChange();
return result;
}
});
}
基本的に、書くときは、 user.address.city = 'Milano'、次のようなことが起こります。
- 外部プロキシがインターセプトする
.addressネストされたプロキシを返しますaddress - ネストされたプロキシのインターセプト
.city = 'Milano' - 値を更新し、Svelte の応答システムに通知します。
- Svelte は、に依存するバインディングに対してのみ更新をスケジュールします。
user.address.city
プロキシが機能しない場合
プロキシ経由の応答性が期待どおりに機能しない場合があります。
-
分割すると応答性が失われます。
const { city } = user.address;—cityそして今はプリミティブ文字列です、 プロキシではありません。常に使用するuser.address.cityテンプレート内で直接。 -
スプレッドは反応性を失います:
const copy = { ...user };非リアクティブなコピーを作成します。 -
Map と Set にはカスタム配列の $state.raw が必要です。
すべてのエキゾチックなコレクション メソッドにパッチが適用されているわけではありません。マップ/セットの場合は、次を使用します。
$state(new Map())— これらにもクイック 5 パッチを適用します。
TypeScript を使用した $state: 完全な型付け
$state TypeScript と完全に統合されています。型は自動的に推測されます
初期値から変更しますが、ジェネリックスを使用して明示的に指定することもできます。
<script lang="ts">
// Inferenza automatica del tipo
let count = $state(0); // number
let name = $state(''); // string
let flag = $state(false); // boolean
// Tipo esplicito per casi complessi
interface User {
id: number;
name: string;
role: 'admin' | 'user';
metadata: Record<string, unknown>;
}
let user = $state<User>({
id: 1,
name: 'Federico',
role: 'admin',
metadata: {}
});
// Array con tipo esplicito
let items = $state<string[]>([]);
// Stato nullable (inizializzato a null)
let selectedItem = $state<User | null>(null);
// $state.raw: reattivita superficiale (solo l'assegnamento, non le mutazioni)
// Utile per grandi array dove la performance conta
let bigList = $state.raw<number[]>([]);
function addToList(n: number) {
// Con $state.raw, devi riassegnare (non push)
bigList = [...bigList, n];
}
</script>
$derived: 自動ストレージ
$derived 依存関係が変更された場合にのみ再計算される計算値を作成します。
そして同等のもの useMemo React と同様ですが、依存関係を明示的に宣言する必要はありません —
コンパイラは関数コードを分析することでそれらを自動的に追跡します。
<script lang="ts">
let items = $state([
{ id: 1, name: 'Laptop', price: 1200, category: 'tech' },
{ id: 2, name: 'Mouse', price: 25, category: 'tech' },
{ id: 3, name: 'Desk', price: 450, category: 'furniture' }
]);
let filterCategory = $state('');
let sortBy = $state<'name' | 'price'>('name');
let sortAsc = $state(true);
// $derived traccia automaticamente: items, filterCategory, sortBy, sortAsc
const filteredAndSorted = $derived(() => {
let result = filterCategory
? items.filter(i => i.category === filterCategory)
: [...items];
result.sort((a, b) => {
const mult = sortAsc ? 1 : -1;
if (sortBy === 'name') return a.name.localeCompare(b.name) * mult;
return (a.price - b.price) * mult;
});
return result;
});
// Dipende solo da filteredAndSorted
const totalValue = $derived(
filteredAndSorted.reduce((sum, item) => sum + item.price, 0)
);
// Dipende da items direttamente
const categories = $derived([...new Set(items.map(i => i.category))]);
</script>
Svelte 5 は プッシュプルシステム メモ化の場合: 依存関係が変更されたとき、
$derived は「ダーティ」(プッシュ) としてマークされていますが、値はすぐには再計算されません。
誰かがそれを読んだ(プルした)ときにのみ再計算されます。誰も導出値を読まない場合、計算は行われません。
$derived.by: 関数を使用した複雑な計算
複数の式が必要な計算の場合は、次を使用します。 $derived.by() 彼はそれを受け入れます
本体のある関数:
<script lang="ts">
let transactions = $state([
{ amount: 100, type: 'income', date: new Date('2026-01-15') },
{ amount: -50, type: 'expense', date: new Date('2026-01-16') },
{ amount: 200, type: 'income', date: new Date('2026-02-01') },
{ amount: -75, type: 'expense', date: new Date('2026-02-15') }
]);
// Per computazioni multi-step, usa $derived.by
const stats = $derived.by(() => {
const income = transactions
.filter(t => t.type === 'income')
.reduce((sum, t) => sum + t.amount, 0);
const expenses = transactions
.filter(t => t.type === 'expense')
.reduce((sum, t) => sum + Math.abs(t.amount), 0);
const balance = income - expenses;
const byMonth = transactions.reduce((acc, t) => {
const key = t.date.toISOString().slice(0, 7); // YYYY-MM
acc[key] = (acc[key] ?? 0) + t.amount;
return acc;
}, {} as Record<string, number>);
return { income, expenses, balance, byMonth };
});
</script>
<p>Saldo: {stats.balance}</p>
<p>Entrate: {stats.income} — Uscite: {stats.expenses}</p>
TypeScript ファイル内のルーン文字: 革命ニュース
Svelte 4 と Svelte 5 の最も重要な違いは、ルーンの構文ではなく、 ルーンは、.svelte.ts または .svelte.js 拡張子を持つ任意のファイルで機能します。、それだけではありません コンポーネントで。これにより、まったく新しい状態管理パターンが可能になります。
// src/lib/stores/cart.svelte.ts
// Un "store" costruito con i Runes, senza dipendenze da Svelte stores
export interface CartItem {
id: number;
name: string;
price: number;
quantity: number;
}
function createCart() {
let items = $state<CartItem[]>([]);
// Computati derivati dallo stato del carrello
const total = $derived(
items.reduce((sum, item) => sum + item.price * item.quantity, 0)
);
const itemCount = $derived(
items.reduce((sum, item) => sum + item.quantity, 0)
);
const isEmpty = $derived(items.length === 0);
function addItem(item: Omit<CartItem, 'quantity'>) {
const existing = items.find(i => i.id === item.id);
if (existing) {
existing.quantity++; // Mutazione diretta: il Proxy la traccia
} else {
items.push({ ...item, quantity: 1 });
}
}
function removeItem(id: number) {
const index = items.findIndex(i => i.id === id);
if (index !== -1) items.splice(index, 1);
}
function updateQuantity(id: number, quantity: number) {
const item = items.find(i => i.id === id);
if (item) {
if (quantity <= 0) removeItem(id);
else item.quantity = quantity;
}
}
function clear() {
items = [];
}
return {
// Esponi come getter per proteggere l'accesso diretto
get items() { return items; },
get total() { return total; },
get itemCount() { return itemCount; },
get isEmpty() { return isEmpty; },
addItem,
removeItem,
updateQuantity,
clear
};
}
// Singleton: un'istanza condivisa tra tutti i componenti che importano questo modulo
export const cart = createCart();
このストアは、任意の .svelte コンポーネントにインポートして、完全な状態で直接使用できます。 反応性:
<!-- CartSummary.svelte -->
<script lang="ts">
import { cart } from '$lib/stores/cart.svelte';
</script>
{#if cart.isEmpty}
<p>Il carrello e vuoto</p>
{:else}
<p>{cart.itemCount} prodotti — Totale: €{cart.total.toFixed(2)}</p>
{#each cart.items as item (item.id)}
<div>
<span>{item.name}</span>
<button onclick={() => cart.updateQuantity(item.id, item.quantity - 1)}>-</button>
<span>{item.quantity}</span>
<button onclick={() => cart.updateQuantity(item.id, item.quantity + 1)}>+</button>
<button onclick={() => cart.removeItem(item.id)}>Rimuovi</button>
</div>
{/each}
<button onclick={cart.clear}>Svuota carrello</button>
{/if}
TypeScript クラスの $state
ルーンは TypeScript クラスでも機能し、状態管理の OOP スタイル パターンを有効にします。
// src/lib/models/todo-list.svelte.ts
export class TodoList {
// I campi della classe usano $state come inizializzatore
items = $state<{ id: number; text: string; done: boolean }[]>([]);
filter = $state<'all' | 'active' | 'done'>('all');
// $derived come getter calcolato
readonly filtered = $derived.by(() => {
switch (this.filter) {
case 'active': return this.items.filter(i => !i.done);
case 'done': return this.items.filter(i => i.done);
default: return this.items;
}
});
readonly stats = $derived.by(() => ({
total: this.items.length,
done: this.items.filter(i => i.done).length,
active: this.items.filter(i => !i.done).length
}));
private nextId = 1;
add(text: string) {
this.items.push({ id: this.nextId++, text, done: false });
}
toggle(id: number) {
const item = this.items.find(i => i.id === id);
if (item) item.done = !item.done;
}
delete(id: number) {
this.items = this.items.filter(i => i.id !== id);
}
clearDone() {
this.items = this.items.filter(i => !i.done);
}
}
// Puo essere usata come singleton o istanziata per componente
export const todoList = new TodoList();
React useMemo および Vue 計算との比較
依存関係の自動追跡 $derived バグの主な原因を排除します
同等の React および Vue ソリューションでは次のようになります。
// React: dipendenze manuali, fonte di bug
const filteredItems = useMemo(() => {
return items.filter(i => i.category === filter);
}, [items, filter]); // Se dimentichi una dipendenza: bug silenzioso
// Vue 3: tracking automatico, ma sintassi verbosa
const filteredItems = computed(() => {
return items.value.filter(i => i.category === filter.value);
});
// Svelte 5: tracking automatico, sintassi diretta
const filteredItems = $derived(
items.filter(i => i.category === filter)
);
実際の違い: React では、配列内の依存関係を忘れます。 useMemo 生成する
サイレントの古い値。デバッグが難しいことで有名なバグです。 Svelte 5 にはこの問題はありません
追跡は実行時にプロキシ経由で行われるためです。
$state と $derived を使用する場合
- $state を使用する ユーザーのアクションやイベントに応じて変化する値の場合 (入力フィールド、トグルスイッチ、API からロードされたデータ)
- $derived を使用する 既存の $state から計算できるものすべて (合計、フィルター、書式設定、変換)
-
計算に $effect を使用しないでください — 自分が書いていることに気づいたら
$effect(() => { derived = compute(state); })、アメリカ$derivedその代わり
高度なパターン: 共有反応性コンテキスト
複雑なアプリケーションで非常に便利なパターンであり、共有されるリアクティブな「コンテキスト」を作成します。 階層内のコンポーネント間:
// src/lib/context/theme.svelte.ts
import { getContext, setContext } from 'svelte';
const THEME_KEY = Symbol('theme');
export function createThemeContext() {
let isDark = $state(
typeof window !== 'undefined'
? localStorage.getItem('theme') === 'dark'
: false
);
const theme = $derived(isDark ? 'dark' : 'light');
const cssClass = $derived(`theme-${theme}`);
$effect(() => {
if (typeof window !== 'undefined') {
localStorage.setItem('theme', theme);
document.documentElement.className = cssClass;
}
});
function toggle() { isDark = !isDark; }
function setDark(value: boolean) { isDark = value; }
const context = { get isDark() { return isDark; }, get theme() { return theme; }, toggle, setDark };
setContext(THEME_KEY, context);
return context;
}
export function useTheme() {
return getContext<ReturnType<typeof createThemeContext>>(THEME_KEY);
}
結論と次のステップ
$state e $derived これらは Svelte 5 の反応性の 2 本の柱です。
.svelte コンポーネントの外部で機能する機能により、Svelte 4 のアーキテクチャの可能性が開かれます。
許可されていないもの: モジュール状態管理、個別にテスト可能なロジック、反応性のある OOP パターン
統合されており、明示的な Context API を使用せずにコンポーネント間で状態を共有します。
次の記事で考察します $効果 — 副作用のルーン — およびその場合の説明
正しく使用し、代わりに使用する必要がある場合 $derived。それは重要なテーマなので、
の虐待 $effect Svelte 5 の主なアンチパターン。
シリーズ: Svelte 5 およびフロントエンド コンパイラー駆動
- 記事 1: コンパイラ主導のアプローチとメンタルモデル
- 第 2 条 (本): $state と $derived — ルーンによる普遍的な反応性
- 第 3 条: $effect とライフサイクル — いつ使用するか (および使用しない場合)
- 第 4 条: SvelteKit SSR、ストリーミングおよびロード機能
- 第 5 条: Svelte 5 のトランジションとアニメーション
- 第 6 条: Svelte のアクセシビリティ: コンパイラーの警告とベスト プラクティス
- 第 7 条: グローバル状態管理: コンテキスト、ルーン、ストア
- 第 8 条: Svelte 4 から Svelte 5 への移行 — 実践ガイド
- 第 9 条: Svelte 5 でのテスト: Vitest、テスト ライブラリ、および Playwright







