Svelte 5入門 第2回 Svelteコンポーネントの基本

Svelteでアプリケーションを作る上で欠かすことのできない要素である、コンポーネントについて解説します。

発行

著者 宇野 陽太 フロントエンド・エンジニア
Svelte 5入門 シリーズの記事一覧

前回まで

前回はSvelteを使ったプロジェクトのテンプレートを作成し、テンプレートの初期状態を確認して実行することをとおして、Svelteの特徴を解説しました。

その中でも出てきたコンポーネントについて、今回は詳しく取り上げていきます。

Svelteのコンポーネント

コンポーネントは「部品」という意味です。アプリケーションを構成する要素であり、コンポーネントを組み合わせていくことでアプリケーションが作られます。コンポーネント化することで、UIを再利用しやすくしたり、そのUIの責任範囲を明確にできます。この考え方はReactやVue.jsといったメジャーなUIフレームワークでも重要なものです。

そして、Svelteにおいてもこれは同様で、コンポーネントはSvelteでアプリケーションを作る上で欠かすことのできない要素です。

前回でも紹介したように、Svelteのコンポーネントは.svelte拡張子のファイルで作成し、その中身はscriptタグ、styleタグ、コンポーネントのマークアップの3つで構成されています。

Svelteコンポーネントの基本形

<script></script>

<div></div>

<style></style>

これらは順不同ですが、このシリーズの記事では上記のように、script、マークアップ、styleの順番で記述しています。

スクリプト部分を最初にすることでコンポーネントの動作を把握し、スクリプト部分と関わりが深くなるマークアップを次に配置することで互いの行き来をしやすくするのが目的です。スタイル部分は行数が増えがちであり、また、ものによっては存在しなくともコンポーネントは機能するという事情から、最後に配置しています。

この順番については、個人の好みやプロジェクトのルールに応じて変更するとよいでしょう。

また、スクリプト部分をTypeScriptで書きたい場合は、次のように記述します。

TypeScriptの使用

<script lang="ts"></script>

scriptタグに属性を追加するだけですが、これだけでマークアップ部分もTypeScriptで書くことができるようになります。

通常、アプリケーションはコンポーネントを組み合わせ、入れ子構造にして作成していきます。今回はコンポーネント周りの開発を体験するために、小さなコンポーネントを複数作成し、アプリケーションを作ってみましょう。

補足:動作確認

前回のインストール作業で、src/routes/+page.svelteというファイルが作られています。このファイルを書き換えることで、今回のsvelteファイル(コンポーネント)のビルド結果を簡易的に確認することができます。プロジェクトのファイル構成など詳しいことはSvelteKitを解説した以下のシリーズなどを参考にしてください。

コンポーネントのスクリプト

まずは、コンポーネントに記述するスクリプトについて解説します。前述した通り、コンポーネントのスクリプトはscriptタグ内に記述します。ここに記述するJavaScriptは、一部を除いて、ブラウザで実行する普通のJavaScriptと同じものを書きます。

たとえば、ページにヘッダーを表示し、この表示内容を変数で扱うようにしてみます。

ヘッダーのコンテンツを変数で扱う

<script>
  const title = "Svelte";
  const suffix = "😀";
</script>

<h1>Hello, {title}! {suffix}</h1>

titlesuffixという変数を定義し、それぞれ表示したい文字列を代入しています。

マークアップ内でスクリプトの変数を利用する場合は、変数名を{}で囲みます。ReactやVue.jsを使ったことがある方は、おなじみの記法です。

また、ヘッダーの文字列を動的に生成するような関数を作ることもできます。

動的に文字列を生成する

<script>
  const makeHeader = (title, suffix) => {
    return `Hello, ${title}! ${suffix}`;
  };
</script>

<h1>{makeHeader("Svelte", "😀")}</h1>

先ほどの文字列を生成する関数を作成し、マークアップ内で呼び出しています。ビルド結果もまったく同じになります。

このようにコンポーネントのスクリプトでは、普通のJavaScriptと同じように、変数や関数を定義して、好きなように処理することができます。

コンポーネントの状態

Webアプリケーションを作る場合、ユーザーからの操作を受けて画面の表示を変える、という処理がほぼ必須となります。

たとえば、ボタンを押したときにランダムにsuffixを変えるようにしたいとします。上記のコードを書き換えると、次のようになるでしょう。

ユーザー操作を受けて画面の表示を変えたい

<script>
  const title = "Svelte";
  let suffix = "😀";

  const changeSuffix = () => {
    const emojis = ["😀", "🔥", "🌊", "🌲", "💰", "⛰️", "😎"];
    suffix = emojis.at(Math.floor(Math.random() * emojis.length));
  };
</script>

<h1>Hello, {title}! {suffix}</h1>
<button onclick={changeSuffix}>Change Emoji</button>

しかし、このコードを実行し、ボタンをクリックしてもsuffixの値は変わりません(コラム「後方互換を無効に設定する」を参照)。なぜなら、Svelteは変数の値が変わったことを認識できないからです。

ユーザーからの操作を受け付けるにはどうしたらいいのでしょう。

Svelteコンポーネントの状態

ユーザー操作などを画面に描画するための一時的な値のことを「状態(state)」といいます。一般的なUIライブラリやフレームワークでは、状態が変化すると、それに追従してUIも変化するような仕組みが提供されています。

Svelteもその例に漏れず、「状態」を扱う変数の中身が変更されると、その変更に応じてUIも自動的に更新されます。

Svelteでは、ある変数が「状態」を扱っていることを示すため、その変数の値を$stateでマークします。

たとえば、先程の例であれば、suffixはユーザーの操作を受ける「状態」になるので、これに$stateを使ってマークします。

$stateの使用

<script>
  const title = "Svelte";
  let suffix = $state("😀");

  const changeSuffix = () => {
    const emojis = ["😀", "🔥", "🌊", "🌲", "💰", "⛰️", "😎"];
    suffix = emojis.at(Math.floor(Math.random() * emojis.length));
  };
</script>

<h1>Hello, {title}! {suffix}</h1>
<button onclick={changeSuffix}>Change Emoji</button>

suffix$stateでマークすることで、その後変化しうる「状態」であることを示すようにしました。

初期値は"😀"で、ボタンをクリックするとランダムに選ばれた他の絵文字に変化します。

$stateはSvelte独自の構文です。この構文は、JavaScriptの関数のように見えますが、そうではありません。これは、Svelteコンポーネントのコンパイル時に、コンパイラが辿るためのキーワードです。

繰り返しになりますが、JavaScriptの関数(変数)ではないので、$stateを使うときにimportする必要はありませんし、$stateを変数に格納したり、関数の引数として渡すこともできません。

Svelteでは、$stateのようなキーワードをRunes(ルーン)と呼んでいます。

Runesについて詳しく知る

Runesについてもっと詳しく知りたい方は、公式ドキュメントや、公式ブログでの紹介が参考になります。こちらもご覧ください。

旧バージョンとの互換性に注意

上記のサンプルコードですが、実は以下のように$stateを使わずに書いても動いてしまいます。

$stateを使わなくても動作する

<script>
  const title = "Svelte";
  let suffix = "😀";

  const changeSuffix = () => {
    const emojis = ["😀", "🔥", "🌊", "🌲", "💰", "⛰️", "😎"];
    suffix = emojis.at(Math.floor(Math.random() * emojis.length));
  };
</script>

<h1>Hello, {title}! {suffix}</h1>
<button onclick={changeSuffix}>Change Emoji</button>

旧バージョンでは、letで変数を宣言するだけで、その変数は状態として扱われ、値の変更がUIに反映されていました。後方互換のために、この書き方でも動くようになっていますが、今後のアップデート次第では、この書き方では動かなくなる可能性もあります。

ですが、意図せず書き忘れてしまった場合でも、エラーや警告もなく動いてしまうので困ってしまいます。この後方互換性は、svelte.config.jsに次の設定を加えることで無効化できます。

後方互換を無効に設定する

const config = {
  // 中略
  compilerOptions: { runes: true }
};

export default config;

compilerOptions.runestrueにすることで、$stateを使っていない変数を、UI操作によって書き換えた場合、UIに反映されなくなります。

開発サーバーで実行中であれば、開発サーバーのログに警告も出力されるようになります。

16:23:28 [vite-plugin-svelte] src/routes/+page.svelte:3:6 `suffix` is updated, but is not declared with `$state(...)`. Changing its value will not correctly trigger updates
https://svelte.dev/e/non_reactive_update

ただし、この設定は執筆時点で公式ドキュメントには記載がありません。今後記載される可能性もあれば、逆に今後のバージョンでは使えなくなる可能性もあります。

もし有効にする場合は、ドキュメントをまめにチェックするなど、最新の情報を確認するようにしてください。

まとめ

今回は、Svelteのコンポーネントの基本を解説しました。コンポーネントはscriptタグ、マークアップ、styleタグで構成され、通常のJavaScriptを使って動的なUIを構築できます。

また、Svelte独自の構文であるRunesについても触れました。Runesのひとつである$stateを使うことで、コンポーネントに状態を持たせることができます。

状態を扱うRunesには、$state以外もあります。次回以降順番に解説していきます。

次回は、コンポーネントの分割と、コンポーネントへの状態の受け渡しについて解説します。