Svelte 5 完全リファレンス
Svelte 5の特徴
なぜSvelte 5なのか
- コンパイル時最適化: ランタイムオーバーヘッドを最小限に
- 仮想DOMなし: 直接DOMを操作し高速なレンダリング
- 明示的なリアクティビティ: Runesによる予測可能な状態管理
- TypeScript完全対応: 型安全な開発環境
- 少ないボイラープレート: シンプルで読みやすいコード
Runesシステム
$state - リアクティブ状態
// プリミティブ値
let count = $state(0);
let message = $state<string>('Hello');
// オブジェクト(深いリアクティビティ)
let user = $state({
name: '太郎',
age: 25,
preferences: {
theme: 'dark',
language: 'ja'
}
});
// 配列
let todos = $state<Todo[]>([]);
// クラスインスタンス
class Counter {
value = $state(0);
increment() {
this.value++;
}
reset() {
this.value = 0;
}
}
let counter = new Counter();
typescript
$state.frozen - 読み取り専用
let config = $state.frozen({
apiUrl: 'https://api.example.com',
version: '1.0.0'
});
// ❌ エラー: Cannot assign to read only property
// config.apiUrl = 'new-url';
// ✅ 新しいオブジェクトで置き換える
config = $state.frozen({ ...config, version: '1.0.1' });
typescript
$state.snapshot - スナップショット
let state = $state({ count: 0, items: [] });
// 現在の状態のスナップショットを取得
const snapshot = $state.snapshot(state);
localStorage.setItem('appState', JSON.stringify(snapshot));
typescript
$derived - 計算値
// シンプルな計算
let area = $derived(width * height);
// 複数の依存関係
let subtotal = $derived(price * quantity);
let tax = $derived(subtotal * taxRate);
let total = $derived(subtotal + tax);
typescript
$derived.by - 複雑な計算
let processedItems = $derived.by(() => {
// フィルタリング
let filtered = items.filter(item =>
item.name.toLowerCase().includes(filter.toLowerCase())
);
// ソート
return filtered.sort((a, b) => {
if (sortBy === 'name') {
return a.name.localeCompare(b.name);
}
return a.price - b.price;
});
});
typescript
$effect - 副作用
// 基本的な副作用
$effect(() => {
console.log(`Count changed to: ${count}`);
document.title = `Count: ${count}`;
});
// クリーンアップ付き
$effect(() => {
const timer = setInterval(() => {
console.log('Tick');
}, 1000);
return () => clearInterval(timer);
});
// 非同期処理
$effect(() => {
const controller = new AbortController();
fetch(`/api/data?count=${count}`, {
signal: controller.signal
})
.then(res => res.json())
.then(data => {
message = data.message;
});
return () => controller.abort();
});
typescript
$effect.pre - DOM更新前に実行
let value = $state('');
let previousValue = $state('');
$effect.pre(() => {
previousValue = value;
});
typescript
$effect.tracking - トラッキング確認
$effect(() => {
if ($effect.tracking()) {
console.log('This is reactive');
console.log(tracked); // 依存関係として追跡される
}
untrack(() => {
console.log(untracked); // 追跡されない
});
});
typescript
$effect.root - エフェクトスコープ
onMount(() => {
const cleanup = $effect.root(() => {
let count = $state(0);
$effect(() => {
console.log(`Count: ${count}`);
});
const interval = setInterval(() => {
count++;
}, 1000);
return () => clearInterval(interval);
});
return cleanup;
});
typescript
$props - コンポーネントプロパティ
interface Props {
title: string;
count?: number;
variant?: 'primary' | 'secondary';
onClose?: () => void;
}
let {
title,
count = 0,
variant = 'primary',
onClose,
...restProps
}: Props = $props();
typescript
childrenとSnippets
import type { Snippet } from 'svelte';
interface Props {
title: string;
children?: Snippet;
header?: Snippet<[string]>;
footer?: Snippet;
}
let { title, children, header, footer }: Props = $props();
typescript
<div class="container">
{#if header}
{@render header(title)}
{:else}
<h1>{title}</h1>
{/if}
<main>
{@render children?.()}
</main>
{#if footer}
<footer>
{@render footer()}
</footer>
{/if}
</div>
svelte
$bindable - 双方向バインディング
// 子コンポーネント
interface Props {
value: string;
checked?: boolean;
}
let {
value = $bindable(''),
checked = $bindable(false)
}: Props = $props();
typescript
<!-- 親コンポーネント -->
<ChildComponent bind:value={text} bind:checked={isChecked} />
svelte
$inspect - デバッグ
let count = $state(0);
// 開発環境でのみ動作
$inspect(count); // count: 0
// ラベル付き
$inspect('カウント', count);
// カスタムロガー
$inspect(count).with((type, value) => {
if (type === 'update') {
console.log(`Count updated to: ${value}`);
}
});
typescript
$host - カスタムエレメント
<svelte:options customElement="my-counter" />
<script lang="ts">
// カスタムエレメントのホスト要素にアクセス
$host().addEventListener('custom-event', (e) => {
console.log('Custom event received', e);
});
// ホスト要素のスタイル設定
$effect(() => {
$host().style.setProperty('--count', String(count));
});
</script>
typescript
コンポーネント構造
基本構造
<!-- MyComponent.svelte -->
<script lang="ts">
// TypeScriptコード
import type { Snippet } from 'svelte';
// Props定義
interface Props {
title: string;
count?: number;
children?: Snippet;
}
let { title, count = 0, children }: Props = $props();
// リアクティブな状態
let internalCount = $state(count);
// 計算値
let doubled = $derived(internalCount * 2);
// メソッド
function increment() {
internalCount++;
}
</script>
<!-- HTMLテンプレート -->
<div class="component">
<h2>{title}</h2>
<p>Count: {internalCount}, Doubled: {doubled}</p>
<button onclick={increment}>Increment</button>
{@render children?.()}
</div>
<!-- スタイル -->
<style>
.component {
padding: 1rem;
border: 1px solid #ccc;
border-radius: 8px;
}
</style>
svelte
スクリプトコンテキスト
<!-- モジュールコンテキスト(一度だけ実行) -->
<script context="module" lang="ts">
let totalInstances = 0;
export function resetInstances() {
totalInstances = 0;
}
</script>
<!-- インスタンスコンテキスト(コンポーネントごと) -->
<script lang="ts">
totalInstances++;
const instanceId = totalInstances;
</script>
svelte
テンプレート構文
条件分岐
{#if loading}
<p>読み込み中...</p>
{:else if error}
<p>エラー: {error.message}</p>
{:else if data}
<div>{data.content}</div>
{:else}
<p>データがありません</p>
{/if}
svelte
ループ処理
<!-- 基本的なeach -->
{#each items as item (item.id)}
<Item {item} />
{/each}
<!-- インデックス付き -->
{#each items as item, index (item.id)}
<div>{index + 1}. {item.name}</div>
{/each}
<!-- 分割代入 -->
{#each users as { id, name, email } (id)}
<User {name} {email} />
{/each}
<!-- 空の場合の処理 -->
{#each items as item}
<li>{item}</li>
{:else}
<p>アイテムがありません</p>
{/each}
svelte
非同期処理
{#await promise}
<Loading />
{:then data}
<Success {data} />
{:catch error}
<Error {error} />
{/await}
<!-- 成功のみ -->
{#await promise then data}
<div>{data}</div>
{/await}
svelte
キー付きブロック
{#key value}
<div transition:fade>
Value: {value}
</div>
{/key}
svelte
HTMLコンテンツ
<!-- HTML文字列をレンダリング(XSS注意) -->
<div>{@html htmlContent}</div>
<!-- デバッグ出力 -->
{@debug value}
svelte
イベント処理
基本的なイベント
<!-- インラインハンドラー -->
<button onclick={() => console.log('Clicked!')}>
Click me
</button>
<!-- メソッド参照 -->
<button onclick={handleClick}>
Click me
</button>
<!-- イベント修飾子 -->
<button onclick|preventDefault|stopPropagation={handleClick}>
Submit
</button>
<!-- 一度だけ実行 -->
<button onclick|once={() => alert('Once!')}>
Click Once
</button>
svelte
カスタムイベント
// 子コンポーネント
import { createEventDispatcher } from 'svelte';
const dispatch = createEventDispatcher<{
message: { text: string };
delete: { id: number };
}>();
function sendMessage() {
dispatch('message', { text: 'Hello!' });
}
typescript
バインディング
入力要素
<input bind:value={text} />
<input type="number" bind:value={number} />
<input type="checkbox" bind:checked />
<select bind:value={selected}>
<option value="a">A</option>
<option value="b">B</option>
</select>
<!-- グループバインディング -->
{#each items as item}
<label>
<input type="checkbox" bind:group={selectedItems} value={item} />
{item.name}
</label>
{/each}
svelte
要素プロパティ
<!-- 要素への参照 -->
<input bind:this={inputElement} />
<!-- 読み取り専用プロパティ -->
<div bind:clientWidth={width} bind:clientHeight={height}>
Resizable content
</div>
<!-- ウィンドウバインディング -->
<svelte:window
bind:innerWidth
bind:innerHeight
bind:scrollY
/>
svelte
スタイリング
Scopedスタイル
<style>
/* このコンポーネントにのみ適用 */
p {
color: purple;
}
/* グローバルスタイル */
:global(body) {
margin: 0;
}
/* 子要素のグローバルスタイル */
div :global(strong) {
color: red;
}
</style>
svelte
動的スタイル
<!-- style:property -->
<div
style:color={color}
style:font-size="{size}px"
style:font-weight={bold ? 'bold' : 'normal'}
>
Style directives
</div>
<!-- CSS変数 -->
<div style:--theme-color={color}>
<p>Uses CSS variable</p>
</div>
svelte
クラスディレクティブ
<!-- class:name -->
<div class:active class:important>
Conditional classes
</div>
<!-- class:name={value} -->
<div
class:active={isActive}
class:disabled={!enabled}
>
Conditional with expression
</div>
svelte
🎬 トランジションとアニメーション
ビルトイントランジション
import { fade, fly, slide, scale, blur } from 'svelte/transition';
import { quintOut } from 'svelte/easing';
<div transition:fade={{ duration: 500 }}>
Fades in and out
</div>
<div transition:fly={{ x: 200, duration: 500, easing: quintOut }}>
Flies in
</div>
<!-- in/out別々 -->
<div in:fly={{ x: -200 }} out:fade>
Different transitions
</div>
typescript
カスタムトランジション
function typewriter(node: HTMLElement, { speed = 1 }) {
const text = node.textContent!;
const duration = text.length / (speed * 0.01);
return {
duration,
tick: (t: number) => {
const i = Math.trunc(text.length * t);
node.textContent = text.slice(0, i);
}
};
}
typescript
アニメーション
import { flip } from 'svelte/animate';
{#each items as item (item.id)}
<div animate:flip={{ duration: 250 }}>
{item.name}
</div>
{/each}
typescript
モーション
import { spring, tweened } from 'svelte/motion';
import { cubicOut } from 'svelte/easing';
const coords = spring({ x: 0, y: 0 }, {
stiffness: 0.1,
damping: 0.25
});
const progress = tweened(0, {
duration: 400,
easing: cubicOut
});
typescript
Actions
基本的なAction
function tooltip(node: HTMLElement, text: string) {
const tooltipEl = document.createElement('div');
tooltipEl.textContent = text;
tooltipEl.className = 'tooltip';
function show() {
document.body.appendChild(tooltipEl);
}
function hide() {
tooltipEl.remove();
}
node.addEventListener('mouseenter', show);
node.addEventListener('mouseleave', hide);
return {
update(newText: string) {
tooltipEl.textContent = newText;
},
destroy() {
node.removeEventListener('mouseenter', show);
node.removeEventListener('mouseleave', hide);
tooltipEl.remove();
}
};
}
typescript
<button use:tooltip={'Click me!'}>
Hover me
</button>
svelte
特殊要素
svelte:self - 再帰コンポーネント
<!-- Tree.svelte -->
<script lang="ts">
interface TreeNode {
name: string;
children?: TreeNode[];
}
let { node }: { node: TreeNode } = $props();
</script>
<li>
{node.name}
{#if node.children}
<ul>
{#each node.children as child}
<svelte:self node={child} />
{/each}
</ul>
{/if}
</li>
svelte
svelte:component - 動的コンポーネント
<script lang="ts">
import type { ComponentType, SvelteComponent } from 'svelte';
let currentComponent = $state<ComponentType<SvelteComponent>>(ComponentA);
</script>
<svelte:component this={currentComponent} {...componentProps} />
svelte
svelte:element - 動的要素
<svelte:element this={tag} class="dynamic">
Dynamic element
</svelte:element>
svelte
svelte:window,, svelte:body,, svelte:document,, svelte:head
<svelte:window bind:scrollY onkeydown={handleKeydown} />
<svelte:body onmouseenter={() => console.log('Mouse entered')} />
<svelte:document onvisibilitychange={() => console.log('Visibility changed')} />
<svelte:head>
<title>Page Title</title>
<meta name="description" content="Page description" />
</svelte:head>
svelte
TypeScript統合
コンポーネント型定義
import type { ComponentType, SvelteComponent, ComponentProps } from 'svelte';
// コンポーネント型
type ButtonComponent = ComponentType<SvelteComponent<{
variant?: 'primary' | 'secondary';
disabled?: boolean;
}>>;
// プロパティの抽出
type ButtonProps = ComponentProps<Button>;
type NativeButtonProps = ComponentProps<'button'>;
// 組み合わせ
type CustomButtonProps = ComponentProps<'button'> & {
variant?: 'primary' | 'secondary';
size?: 'sm' | 'md' | 'lg';
};
typescript
イベント型定義
interface CustomEvents {
submit: CustomEvent<{ data: FormData }>;
cancel: CustomEvent<void>;
}
function handleClick(event: MouseEvent & { currentTarget: HTMLButtonElement }) {
console.log(event.currentTarget.textContent);
}
typescript
パフォーマンス最適化
untrack - 依存関係の除外
import { untrack } from 'svelte';
$effect(() => {
console.log('Trigger:', trigger);
// unrelated の変更では再実行されない
untrack(() => {
console.log('Unrelated:', unrelated);
});
});
typescript
メモ化パターン
const cache = new Map();
let filteredItems = $derived.by(() => {
const key = `${searchTerm}-${items.length}`;
if (cache.has(key)) {
return cache.get(key);
}
const result = items.filter(item =>
item.name.toLowerCase().includes(searchTerm.toLowerCase())
);
cache.set(key, result);
// キャッシュサイズ制限
if (cache.size > 10) {
const firstKey = cache.keys().next().value;
cache.delete(firstKey);
}
return result;
});
typescript
デバウンス処理
$effect(() => {
const timer = setTimeout(async () => {
if (searchInput.length > 2) {
const res = await fetch(`/api/search?q=${searchInput}`);
searchResults = await res.json();
}
}, 300);
return () => clearTimeout(timer);
});
typescript
遅延読み込み
import { onMount } from 'svelte';
let HeavyComponent: ComponentType<SvelteComponent> | null = null;
onMount(async () => {
const module = await import('./HeavyComponent.svelte');
HeavyComponent = module.default;
});
typescript
テスト
コンポーネントテスト
import { render, fireEvent } from '@testing-library/svelte';
import { expect, test, vi } from 'vitest';
import Button from './Button.svelte';
test('renders button with text', () => {
const { getByText } = render(Button, {
props: { text: 'Click me' }
});
expect(getByText('Click me')).toBeInTheDocument();
});
test('calls onclick handler', async () => {
const handleClick = vi.fn();
const { getByRole } = render(Button, {
props: {
text: 'Click me',
onclick: handleClick
}
});
const button = getByRole('button');
await fireEvent.click(button);
expect(handleClick).toHaveBeenCalledTimes(1);
});
typescript
Svelte 4からの移行
主な変更点
// === 状態管理 ===
// Svelte 4
let count = 0;
$: doubled = count * 2;
// Svelte 5
let count = $state(0);
let doubled = $derived(count * 2);
// === Props ===
// Svelte 4
export let prop: string;
// Svelte 5
let { prop }: { prop: string } = $props();
// === Slots ===
// Svelte 4
<slot />
// Svelte 5
{@render children?.()}
typescript
移行チェックリスト
-
let
宣言を$state()
に変更 -
$:
リアクティブ文を$derived
または$effect
に変更 -
export let
を$props()
に変更 -
<slot />
を{@render children?.()}
に変更 - ストアの
$
プレフィックスを削除
関連リソース
- Svelte完全ガイド - Svelteの基礎から応用まで
- SvelteKit 2.x 完全リファレンス - SvelteKitの詳細
- 実装例 - 実践的なサンプルコード
まとめ
Svelte 5は、Runesシステムにより明示的で型安全なリアクティビティを提供します。TypeScriptとの完全な統合により、保守性が高く、パフォーマンスに優れたアプリケーションを構築できます。
On this page
Svelte 5の特徴なぜSvelte 5なのかRunesシステム$state - リアクティブ状態$state.frozen - 読み取り専用$state.snapshot - スナップショット$derived - 計算値$derived.by - 複雑な計算$effect - 副作用$effect.pre - DOM更新前に実行$effect.tracking - トラッキング確認$effect.root - エフェクトスコープ$props - コンポーネントプロパティchildrenとSnippets$bindable - 双方向バインディング$inspect - デバッグ$host - カスタムエレメントコンポーネント構造基本構造スクリプトコンテキストテンプレート構文条件分岐ループ処理非同期処理キー付きブロックHTMLコンテンツイベント処理基本的なイベントカスタムイベントバインディング入力要素要素プロパティスタイリングScopedスタイル動的スタイルクラスディレクティブ🎬 トランジションとアニメーションビルトイントランジションカスタムトランジションアニメーションモーションActions基本的なAction特殊要素svelte - 再帰コンポーネントsvelte - 動的コンポーネントsvelte - 動的要素svelte, svelte, svelte, svelteTypeScript統合コンポーネント型定義イベント型定義パフォーマンス最適化untrack - 依存関係の除外メモ化パターンデバウンス処理遅延読み込みテストコンポーネントテストSvelte 4からの移行主な変更点移行チェックリスト関連リソースまとめ