SvelteでUIを実装する際、部品を共通化するためにコンポーネントとしてファイルを分割するかどうか、判断に迷う場面があります。特に、数行程度の小さなマークアップを共通化したい場合に、そのためだけに新しいファイルを作成することが、プロジェクト全体の管理性を考えると過剰に感じられるケースです。
従来のコンポーネント分割における課題
例えば商品カードのようなUIの各パーツは、それぞれ個別のコンポーネントファイルに分割するのが一般的な方法でした。
<script>
import ProductImage from './ProductImage.svelte';
import ProductMeta from './ProductMeta.svelte';
let { product } = $props();
</script>
<div class="card">
<ProductImage {product} />
<ProductMeta {product} />
</div>
このアプローチは再利用性に優れているのですが、ごく小さなUIのためにファイルが増え、結果としてプロジェクトが煩雑になるという側面がありました。
snippet
による解決策
snippet
を用いると、これらのUI部品を単一のコンポーネントファイル内に、再利用可能な形で定義することができます。
<script>
let { product } = $props();
</script>
{#snippet productImage(product)}
<img src={product.image} alt={product.name} />
{/snippet}
{#snippet productMeta(product)}
<h2>{product.name}</h2>
<p>¥{product.price}</p>
{/snippet}
<div class="card">
{@render productImage(product)}
{@render productMeta(product)}
</div>
関連するマークアップが同一ファイル内にまとまることで、コンポーネントの見通しが大幅に向上します。ファイル間の移動がなくなり、ロジックの追跡が容易になる点は、開発効率の観点からも大きな利点です。
snippet
が特に有効なパターン
このような解決策は、ループ処理のように、基本構造を共有しつつ一部分だけを動的に変更したいケースです。
<script>
// ...
</script>
{#snippet figure(image)}
<figure>
<img src={image.src} alt={image.caption} />
<figcaption>{image.caption}</figcaption>
</figure>
{/snippet}
{#each images as image}
{#if image.href}
<a href={image.href}>
{@render figure(image)}
</a>
{:else}
{@render figure(image)}
{/if}
{/each}
もしsnippet
を使わなければ、{#if}
と{:else}
の各ブロック内で似たようなコードを重複して記述する必要があり、これは将来の修正漏れやメンテナンスコスト増大の原因となりがちです。snippet
は、このようなコードの重複を構造的に防ぐことができます。
snippet
の適切な使い分け
snippet
は、一見便利なマークアップと感じられるかもしれませんが、必ずしもコンポーネントの代替となるわけではありません。状態管理や複雑なライフサイクルが必要な場合は、従来通りコンポーネントとして分割することが適切です。
しかし、「ステートレスで、単一コンポーネント内でのみ再利用されるマークアップ」に対しては、snippet
はコンポーネント分割よりも適切な選択肢となり得ます。ファイル数を抑え、関連するコードのまとまりを良くするという観点で、設計の引き出しの一つとして持っておくと良いでしょう。