svelte の使い方

Cybernetically enhanced web apps

v5.55.14.4M/週MITフレームワーク
AI生成コンテンツ

この記事はAIによって生成されました。内容の正確性は保証されません。最新の情報は公式ドキュメントをご確認ください。

Svelte の使い方 — コンパイラベースのUIフレームワーク完全ガイド

一言でいうと

Svelteは、宣言的に書いたコンポーネントをビルド時に最適化されたバニラJavaScriptへコンパイルするUIフレームワークです。仮想DOMを使わず、DOMを直接・外科的に更新するため、ランタイムのオーバーヘッドが極めて小さいのが最大の特徴です。

どんな時に使う?

  • パフォーマンス重視のWebアプリケーション開発 — バンドルサイズを最小限に抑えたいSPA・MPA構築時。仮想DOMのランタイムコストを排除できます
  • SvelteKitと組み合わせたフルスタック開発 — SSR/SSG/SPAを柔軟に切り替えられるメタフレームワークSvelteKitの基盤として使用
  • インタラクティブなUIコンポーネントの構築 — リアクティブな状態管理が言語レベルで組み込まれており、少ないボイラープレートで複雑なUIを実装可能

インストール

※ 本記事はSvelte 5系(v5.55.1時点)を対象としています。Svelte 4以前とはリアクティビティの仕組みが大きく異なります。

通常はSvelteKitのプロジェクト作成コマンドを使うのが推奨です:

npx sv create my-app
cd my-app
npm install
npm run dev

既存プロジェクトにSvelte単体を追加する場合:

# npm
npm install svelte

# yarn
yarn add svelte

# pnpm
pnpm add svelte

Viteと組み合わせる場合は @sveltejs/vite-plugin-svelte も必要です:

npm install --save-dev @sveltejs/vite-plugin-svelte

基本的な使い方

Svelteコンポーネントは .svelte ファイルに記述します。HTML・CSS・JavaScriptが1ファイルにまとまる単一ファイルコンポーネント形式です。

最もシンプルなカウンターの例

<!-- Counter.svelte -->
<script lang="ts">
  let count: number = $state(0);

  function increment(): void {
    count++;
  }
</script>

<button onclick={increment}>
  クリック回数: {count}
</button>

<style>
  button {
    padding: 0.5rem 1rem;
    font-size: 1.2rem;
    border-radius: 4px;
    cursor: pointer;
  }
</style>

Propsを受け取るコンポーネント

<!-- Greeting.svelte -->
<script lang="ts">
  interface Props {
    name: string;
    greeting?: string;
  }

  let { name, greeting = 'こんにちは' }: Props = $props();
</script>

<p>{greeting}、{name}さん!</p>

親コンポーネントからの利用

<!-- App.svelte -->
<script lang="ts">
  import Counter from './Counter.svelte';
  import Greeting from './Greeting.svelte';
</script>

<Greeting name="田中" />
<Greeting name="鈴木" greeting="おはよう" />
<Counter />

よく使うAPI — Svelte 5 Runes(ルーン)の使い方

Svelte 5では「Runes」と呼ばれる $ プレフィックス付きのコンパイラ指示子がリアクティビティの中核です。

1. $state — リアクティブな状態の宣言

最も基本的なルーンです。値が変更されると、それを参照しているUIが自動的に更新されます。

<script lang="ts">
  // プリミティブ値
  let name: string = $state('Svelte');

  // オブジェクト(ディープリアクティブ)
  let user = $state({
    name: '田中太郎',
    age: 30,
    hobbies: ['読書', 'プログラミング']
  });

  function addHobby(): void {
    // 配列のpushも検知される(ディープリアクティビティ)
    user.hobbies.push('ゲーム');
  }
</script>

<p>{user.name}({user.age}歳)</p>
<ul>
  {#each user.hobbies as hobby}
    <li>{hobby}</li>
  {/each}
</ul>
<button onclick={addHobby}>趣味を追加</button>

2. $derived — 派生状態(算出値)

他のリアクティブな値から自動計算される値を定義します。依存する値が変わると自動的に再計算されます。

<script lang="ts">
  let items = $state([
    { name: 'りんご', price: 150, quantity: 3 },
    { name: 'バナナ', price: 100, quantity: 5 },
    { name: 'みかん', price: 80, quantity: 10 }
  ]);

  // 単純な派生値
  let totalItems: number = $derived(
    items.reduce((sum, item) => sum + item.quantity, 0)
  );

  // 複雑なロジックには $derived.by を使用
  let totalPrice: number = $derived.by(() => {
    let sum = 0;
    for (const item of items) {
      sum += item.price * item.quantity;
    }
    return sum;
  });
</script>

<p>合計 {totalItems} 個 / {totalPrice.toLocaleString()} 円</p>

3. $effect — 副作用の実行

リアクティブな値の変更に応じて副作用を実行します。コンポーネントのマウント時にも実行され、アンマウント時にはクリーンアップ関数が呼ばれます。

<script lang="ts">
  let query: string = $state('');
  let results: string[] = $state([]);

  // queryが変わるたびにAPIを呼び出す
  $effect(() => {
    if (query.length < 2) {
      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);
      });

    // クリーンアップ関数(次回実行前 or アンマウント時に呼ばれる)
    return () => {
      controller.abort();
    };
  });
</script>

<input type="text" bind:value={query} placeholder="検索..." />
<ul>
  {#each results as result}
    <li>{result}</li>
  {/each}
</ul>

4. $props — コンポーネントプロパティ

親コンポーネントから渡されるプロパティを受け取ります。TypeScriptの型定義と自然に統合できます。

<!-- UserCard.svelte -->
<script lang="ts">
  import type { Snippet } from 'svelte';

  interface Props {
    name: string;
    email: string;
    role?: 'admin' | 'user' | 'guest';
    onDelete?: (name: string) => void;
    children: Snippet;
  }

  let {
    name,
    email,
    role = 'user',
    onDelete,
    children
  }: Props = $props();
</script>

<div class="card">
  <h3>{name}</h3>
  <p>{email}</p>
  <span class="badge">{role}</span>

  <!-- Snippet(子コンテンツ)のレンダリング -->
  {@render children()}

  {#if onDelete}
    <button onclick={() => onDelete(name)}>削除</button>
  {/if}
</div>

親からの利用:

<UserCard
  name="田中太郎"
  email="tanaka@example.com"
  role="admin"
  onDelete={(name) => console.log(`${name}を削除`)}
>
  <p>追加の情報をここに記述できます。</p>
</UserCard>

5. $bindable — 双方向バインディング可能なProp

親コンポーネントから bind: で双方向バインディングできるプロパティを定義します。

<!-- TextInput.svelte -->
<script lang="ts">
  interface Props {
    value: string;
    label?: string;
  }

  let { value = $bindable(''), label = '' }: Props = $props();
</script>

<label>
  {#if label}{label}{/if}
  <input type="text" bind:value />
</label>
<!-- 親コンポーネント -->
<script lang="ts">
  import TextInput from './TextInput.svelte';

  let username: string = $state('');
</script>

<TextInput bind:value={username} label="ユーザー名" />
<p>入力値: {username}</p>

類似パッケージとの比較

特徴Svelte 5React 19Vue 3Solid.js
レンダリング方式コンパイル時変換仮想DOM仮想DOM + コンパイラ最適化コンパイル時変換(Fine-grained)
ランタイムサイズ極小(〜2KB)約40KB+約33KB+約7KB
リアクティビティRunes($state等)Hooks(useState等)Composition API(ref等)Signals(createSignal等)
学習コスト低い中程度中程度中程度
TypeScript対応◎(ネイティブ)
メタフレームワークSvelteKitNext.js / RemixNuxtSolidStart
エコシステム規模非常に大きい大きい小〜中
.svelte独自構文ありなし(JSX).vueありなし(JSX)

選定の指針:

  • エコシステムの豊富さを重視するならReact
  • バンドルサイズとパフォーマンスを最優先するならSvelteまたはSolid
  • 既存のVue資産があるならVue 3
  • 少ないコード量で直感的に書きたいならSvelte

注意点・Tips

1. Svelte 4からの移行に注意

Svelte 5ではリアクティビティの仕組みが根本的に変わりました。letによる暗黙的リアクティビティ(Svelte 4)から、明示的な$state/$derivedルーン(Svelte 5)への移行が必要です。公式のマイグレーションガイドを参照してください。

<!-- ❌ Svelte 4 スタイル(5でも動くがレガシー) -->
<script>
  let count = 0; // 暗黙的リアクティブ
  $: doubled = count * 2; // リアクティブ宣言
</script>

<!-- ✅ Svelte 5 スタイル -->
<script>
  let count = $state(0);
  let doubled = $derived(count * 2);
</script>

2. $effectの使いすぎに注意

$effectはReactのuseEffectと似ていますが、依存配列を手動で指定する必要がありません(自動追跡)。ただし、状態の同期には$derivedを優先し、$effectは本当の副作用(DOM操作、API呼び出し、ログ出力など)にのみ使いましょう。

<script lang="ts">
  let firstName: string = $state('太郎');
  let lastName: string = $state('田中');

  // ❌ $effectで状態を同期するのはアンチパターン
  // let fullName = $state('');
  // $effect(() => { fullName = `${lastName} ${firstName}`; });

  // ✅ $derivedを使う
  let fullName: string = $derived(`${lastName} ${firstName}`);
</script>

3. $stateのディープリアクティビティを理解する

$stateで宣言したオブジェクトや配列はProxyでラップされ、ネストしたプロパティの変更も自動検知されます。ただし、Proxyを意識する場面もあります。

<script lang="ts">
  let items = $state([1, 2, 3]);

  // ✅ 直接変更が検知される
  function addItem() {
    items.push(4);
  }

  // ⚠️ Proxyされたオブジェクトを外部ライブラリに渡す場合、
  // $state.snapshot() で生のオブジェクトを取得する
  function sendToApi() {
    const raw = $state.snapshot(items);
    fetch('/api/items', {
      method: 'POST',
      body: JSON.stringify(raw)
    });
  }
</script>

4. CSSのスコーピングはデフォルトで有効

<style>ブロック内のCSSは自動的にそ

比較記事