Back

Svelteを使いこなすためのベストプラクティス

Svelteを使いこなすためのベストプラクティス

Svelteの基本を習得し、実際のアプリケーション開発に取り組み始めると、公式ドキュメントは各機能が何をするかは説明していても、いつなぜ使うのかについては必ずしも触れていないことに気づくでしょう。本記事では、コンポーネントとリアクティビティの基本を理解していることを前提に、本番コードの保守性・パフォーマンス・可読性を向上させるSvelte 5のベストプラクティスに焦点を当てます。

重要なポイント

  • $stateはUIの更新を駆動する値にのみ使用し、値を変更(mutate)するのではなく置き換える場合は$state.rawを選ぶ。
  • 計算値には$effectよりも$derivedを優先する。$effectは外部システムとの同期に限定して使用する。
  • SSR環境ではモジュールレベルの状態を避ける。型安全でリクエストごとにスコープされた共有状態には、クラスベースの$stateとSvelteのcontext APIを使用する。
  • SvelteKitでは、サーバーサイドのページデータには+page.server.jsを、独立したAPIエンドポイントには+server.jsを使用する。
  • 新規コードでは、レガシーパターンではなくモダンなSvelte 5構文(onclick{#snippet}$props())を採用する。

Svelte 5 Runes:正確に使う

Svelte 5のrunesは主要なリアクティビティモデルですが、どこでも使うことよりも、正しく使うことのほうが重要です。

$stateは、変数がUIの更新を駆動する必要がある場合にのみ使用してください。 それ以外の用途には、通常の変数のほうが軽量で意図も明確です。

状態が大きなオブジェクトや配列で、変更(mutate)ではなく置き換えが行われる場合は、代わりに$state.rawを使用しましょう:

// ❌ 再代入しかしないAPIデータに対する不要なプロキシのオーバーヘッド
let users = $state(await fetchUsers());

// ✅ 変更ではなく置き換える場合はプロキシのコストが発生しない
let users = $state.raw(await fetchUsers());

ネストされたプロパティを直接変更する必要がある場合(例:cart.items[0].quantity++)は$stateを使い、値全体を入れ替える場合は$state.rawを使いましょう。

計算値には$effectよりも$derivedを優先する

これはモダンなSvelte開発で最もよくある間違いの1つです:

let num = $state(0);

// ❌ 避けるべき — 不要な副作用を作り出している
let square = $state(0);
$effect(() => { square = num * num; });

// ✅ 正しい — 宣言的で依存関係も自動追跡される
let square = $derived(num * num);

$effectはエスケープハッチです。D3などの外部システムとの同期に限定して使用し、DOMレベルの統合では{@attach}が自然にフィットする場合はそちらの利用も検討しましょう。

Propsは動的な値として扱う

propsから派生する値は、通常の代入ではなく$derivedを使うべきです:

let { type } = $props();

// ✅ `type`が変わったときに同期する
let color = $derived(type === 'danger' ? 'red' : 'green');

共有モジュールよりも型安全なContextを

コンポーネントのサブツリー間で共有される状態には、モジュールレベルの状態よりもSvelteのcontext APIを使うほうがよいでしょう。SSR環境ではモジュール状態はリクエスト間で持続するため、ユーザー間でデータがリークする可能性があります。

モダンなパターンでは、$stateフィールドを持つクラスを使用します:

// lib/theme.svelte.ts
import { getContext, setContext } from 'svelte';

class ThemeContext {
  current = $state('light');

  toggle() {
    this.current = this.current === 'light' ? 'dark' : 'light';
  }
}

const KEY = Symbol('theme');

export const setTheme = () => setContext(KEY, new ThemeContext());

export const getTheme = () => getContext<ThemeContext>(KEY);

このパターン1つで、型安全性、リアクティブな状態、そして適切なSSRスコープを実現できます。

SvelteKitのデータローディング:適切なパターンを選ぶ

SvelteKitでよく混乱を招くのが、+page.server.js+server.jsの使い分けです:

シナリオ使用するもの
SSRやサーバー専用アクセスを伴うページデータの取得+page.server.jsload()
外部利用向けのAPIエンドポイントの構築+server.js
ハイドレーション後のクライアント専用データonMount + fetch

サーバーアクセス、シークレット、SSRを必要とするページデータには、通常+page.server.jsが適切なデフォルトです。サーバーサイドで実行され、シークレットをクライアントに漏らさず、プログレッシブエンハンスメントを実現するSvelteKitのform actionsともクリーンに統合できます。

ちょっとした実践的な改善

キー付きの{#each}ブロックは、目に見えにくいDOM再利用のバグを防ぎます。インデックスではなく、安定した一意のIDで必ずキー付けしましょう。

**$inspect.trace**はリアクティビティのデバッグで活用されていない機能です。任意の$effect$derived.byの先頭に置けば、どの依存関係が再実行のトリガーになったかが正確に分かります。

再利用可能なマークアップにはSlotよりもSnippetを使いましょう。Snippetは合成しやすく、propsとして渡せるため、コンポーネントAPIがすっきりします。

新規コードではレガシー構文を避けましょう。 on:clickonclickに、<slot>{#snippet}に、export let$props()に置き換えます。これらのパターンはモダンなSvelte 5の慣例と現在のコンパイラの挙動に沿ったものです。

まとめ

Svelte 5は抑制(restraint)に報いるフレームワークです。リアクティビティのスコープを精密に絞り込むほど、つまり$stateを必要な場所だけで使い、$effectの代わりに$derivedを使い、モジュールグローバルではなくcontextを使うほど、アプリケーションは予測可能でパフォーマンスの高いものになります。問題を解決する最もシンプルなリアクティブプリミティブから始め、それで本当に対応しきれないときにだけ、より強力なツールに手を伸ばしましょう。

FAQ

$state.rawは、値の一部を変更(mutate)するのではなく値全体を置き換える予定の場合、たとえば取得したAPIレスポンスや一括で再代入する大きな配列を保存する場合に使用します。リアクティブプロキシのオーバーヘッドをスキップできるため、大きなデータセットでのパフォーマンスが向上します。配列内のアイテムを更新するようなネストされた変更にきめ細かなリアクティビティが必要な場合は、通常の$stateを使用してください。

$derivedは宣言的で、依存関係を自動的に追跡し、常に同期し続ける読み取り専用の値を生成します。一方$effectは副作用として命令的に実行され、タイミングのバグや不要な再実行を引き起こす可能性があるため、推論が難しくなります。$effectは、サードパーティライブラリ、Canvas API、手動でのDOM操作など、Svelteのリアクティビティの外にあるシステムとの同期に限定して使用してください。

SvelteKitのようなSSR環境では安全ではありません。モジュールレベルの状態はサーバーが処理するすべてのリクエスト間で共有されるため、あるユーザーのデータが別のユーザーのセッションに漏れる可能性があります。setContextとgetContextを使うSvelteのcontext APIを利用し、$stateフィールドを持つクラスでバックアップするのが理想的です。これによりリアクティビティと型安全性を保ちながら、状態をリクエストごと・コンポーネントツリーごとにスコープできます。

データが特定のページに属しており、SSR、SEO、サーバーサイドアクセス、form actionsの恩恵を受ける場合は+page.server.jsを使用してください。外部クライアントから利用されるJSON API、Webhook、ページ以外からのfetch呼び出しなど、独立したHTTPエンドポイントが必要な場合は+server.jsを使用します。データが1つのページからのみレンダリングされ、サーバーサイド機能を必要とする場合は、通常+page.server.jsのほうが適切なデフォルトです。

Gain Debugging Superpowers

Unleash the power of session replay to reproduce bugs, track slowdowns and uncover frustrations in your app. Get complete visibility into your frontend with OpenReplay — the most advanced open-source session replay tool for developers. Check our GitHub repo and join the thousands of developers in our community.

OpenReplay