# Svelte を触ってみよう

# 前提知識

  • JavaScriptの基本的な構文
  • HTMLとCSSについての少しの知識

# 事前準備

  • CodeSandbox (opens new window)というサービスを利用することで、適当なブラウザーさえ動けば学習できるようにします
    • CodeSandboxならローカルにエクスポートしたプロジェクトをそのまま動作させることができる。「後で自分の環境にNode.jsをちゃんと入れて試したい!」という時も安心
    • 万が一利用できない状況だった場合はStackBlitz (opens new window)Svelte公式のREPL (opens new window)を利用します

# Svelte とは

日本語の公式サイト (opens new window)チュートリアル (opens new window)冒頭より:

Svelte は、web アプリケーションを構築するためのツールです。他のユーザーインターフェースフレームワークと同様、マークアップ(markup)、スタイル(styles)、振る舞い(behaviours) を組み合わせたコンポーネントでアプリを 宣言的(declaratively) に構築することができます。

これらのコンポーネントは小さくて効率的な JavaScript モジュールに コンパイル されるため、従来の UI フレームワークには付き物だったオーバーヘッドがありません。

アプリ全体を Svelte で作ることもできますし(中略)、既存のコードベースに部分的/段階的に追加することもできます。また、どんな場所でも機能するスタンドアロンパッケージとしてコンポーネントを配布することもできます。

  • HTML・CSS・JavaScriptプラスアルファの構文で.svelteというファイルを書くと、いい感じなHTML・CSS・JavaScriptを生成してくれる!
  • 生成したJavaScriptに含まれるランタイムライブラリーが少ない!
    • ユーザーがダウンロードするJavaScriptが軽い!
  • 生成したHTML・CSS・JavaScriptはSvelte以外のフレームワークの中でも使える!
    • 「ちょっとずつSvelteに移植しよう!」「新しく追加する機能だけSvelte試してみよ!」みたいなことができる!
  • 他のフレームワークよりも短く簡潔に書けることを重視

# 早速書こう

CodeSandboxのプロジェクト作成ページ (opens new window)に移動します:

CodeSandboxのプロジェクト作成画面

右上にある「🔍Search Templates」という検索ボックスに「svelte」と入力すると、次のようにSvelte製のアプリケーションを作るテンプレートがたくさんヒットします:

CodeSandboxのプロジェクト作成画面における「svelte」の検索結果

検索結果の2番目次のように「Official」と書かれたものをクリックすると、テンプレートから動作するアプリがすぐに作成されます!

Official Svelte CodeSandbox

CodeSandboxのSvelteテンプレートで作成したプロジェクト(初期状態):

利用した開発環境によって変わりますが、CodeSandboxを使って上記👆のスクリーンショットにした状態について、画面上にあるものを解説します:

  • 画面中央左にあるのが、プロジェクトにあるファイルの一覧です。CodeSandboxもStackBlitzも、おなじみVisual Studio Codeをブラウザー上で動かすことで実現しています
  • 画面真ん中上に表示されているのが、今開いているファイルを編集する画面です。今開いているファイル(index.jssrc/main.js)では、App.svelteというファイルに書かれたコンポーネント(後述)を指定した要素に適用しています
    • ※Svelte REPLの場合初期状態で開かれているのはApp.svelte
  • 画面真ん中下に表示されているのが、作成したアプリケーションを実行している画面です。編集画面でファイルを変更・保存する度に更新され、都度動作確認できます

# 🚩チェックポイント

  • CodeSandboxなどのブラウザー上で動く開発環境を利用して、Svelte製のアプリケーションをテンプレートから作成できた
  • CodeSandboxなどのブラウザー上で動く開発環境について、画面にあるものを大まかに理解できた

# その1 ほぼただのHTML

ここからは、App.svelte(StackBlitzの場合src/App.svelte)というファイルを編集することで、Svelteにおけるコンポーネントの作成方法を紹介します。

App.svelte:

<h1>Hello IIJ Bootcamp!</h1>
1

Svelteのコンポーネントは、表示するHTML、CSSと、そのHTML(を表すDOMツリー)を操作するJavaScriptを、独立して再利用可能な形で一つにまとめたファイルです。最も単純な(でもあまり役に立たない)コンポーネントは、上記のように表示するHTMLだけで構成されています。結果できあがるものはもちろん書いたタグの通りのHTMLです:

Hello IIJ Bootcamp!

# その2 変数の中身を表示

次は、コンポーネントの中にJavaScriptのコードを書いて、JavaScriptのコードで設定した変数を、HTMLの中で表示させてみましょう:

App.svelte:

<script>
    let name = "<誰か適当に挨拶したい人>";
</script>

<h1>Hello {name}!</h1>
1
2
3
4
5

.svelteファイルでは<script>タグの中でコンポーネント専用のJavaScriptを書きます。let [変数名] = ...などの構文で定義した変数はHTML内で波括弧{}で囲うことで参照できます。もちろん<誰か適当に挨拶したい人>の箇所を適当な人の名前に変えて挨拶するのでも構いません。

例:

Hello 鈴木 幸一さん!

# その3 イベントハンドラーを設定

とはいえ、この時点ではまだ普通のHTMLを書くのと大して変わらず、つまらないでしょう。と、いうわけでイベントハンドラーを設定することで、HTMLで何かイベントが発生したとき初めて実行するJavaScriptを定義してみましょう。

App.svelte:

<script>
    function clicked() {
        alert("押しましたね!?");
    }
</script>

<button on:click={clicked}>
ぼたん
</button>
1
2
3
4
5
6
7
8
9

on:clickのように、on:[イベント名]という形式のHTMLの属性は、.svelte専用の属性で、Svelteはon:[イベント名]という属性で指定したJavaScriptの関数をイベントハンドラーとして設定してくれます。

DETAILS

標準のイベントハンドラー用の属性(onclickなど)ではJavaScriptの式を直接文字列として指定する一方、on:clickでは実行するJavaScriptの関数を表す式を設定します。また、例におけるclickedや前述のnameのように、コンポーネントの<script>で設定した変数や関数を参照できるのは、on:clickなどの属性のみとなっております。

動作例:

ぼたん

# 🚩ここまでにできたこと

  • HTMLとJavaScriptで、Svelteのコンポーネントを定義できた
  • Svelteのコンポーネントの中では、{... JavaScriptの式 ...}という構文でJavaScriptの式を参照することを理解できた
  • Svelteのコンポーネントにおいてイベントハンドラーを設定するときは、on:[イベント名]という名前の属性を用いることを理解できた

# その4 変数が変わったらHTMLの中身も変わる

ここまでで紹介したSvelteの機能は、実はSvelteを使わない、普通のJavaScriptだけでも容易に実現できます。ここからはそれでは難しい、Svelteなら簡単に実装できる機能を学習しましょう。

App.svelte:

<script>
    let count = 0;
    function incrementCount() {
        count += 1;
    }
</script>

<button on:click={incrementCount}>
{count}回押しました!
</button>
1
2
3
4
5
6
7
8
9
10

<script>においてletで定義した変数は、同じコンポーネントに書いたHTMLで{... JavaScriptの式 ... }という構文でアクセスできることは以前のステップで学びました。<script>においてletで定義した変数はそれだけでなく、変数の中身を変えるとそれに合わせて{...}で参照している箇所も変える、ということもできます!

上記の例では、buttonをクリックするとincrementCount関数が呼び出されて、count += 1という式で変数countを書き換えます。するとSvelteのランタイムライブラリーがcountを参照している箇所を自動で書き換えてくれます。なので、クリックする度に{count}回押しました!{count}が、ボタンを押した回数に変わります!

動作例:

0回押しました!

# その5 変数が変わる度に実行される宣言・文

letで定義した変数は、HTMLに書いたイベントハンドラーを契機に変更されると、HTMLにその変更を伝達するようになっていることを学びました。では、HTMLではなく、<script>に書いた任意のコードに、letで定義した変数の変更を伝達するにはどうすればよいでしょうか?例えばそれは、「letで定義したあの変数xに併せて変わる変数yを定義したい!(※)」とか、「letで定義したあの変数zが何かしら変わる度に中身をconsole.logで表示したい!」といった場合に最適です。そんな場合は、$:で始まる文を<script>に書きます:

Vue.jsの経験がある方へ

letで定義したあの変数xに併せて変わる変数yを定義したい!」を実現する機能は、要するにVue.jsで言うところの「computed properties」に相当するものと考えて差し支えありません。

App.svelte:

<script>
    let count = 0;
    function incrementCount() {
        count += 1;
    }

    $: plus4 = count + 4;
    $: console.log("変数が変わった!", { plus4, count });
</script>

<button on:click={incrementCount}>
{count}
</button>
+ 4 = {plus4} だ!
1
2
3
4
5
6
7
8
9
10
11
12
13
14

Svelteにおいて$:で始まる文は特別なサインです。$: plus4 = count + 4;のように、$: [変数名] = [JavaScriptの式];という構文で定義した変数は、[JavaScriptの式]に含まれているletで定義した変数の変更に沿って変わります。$: console.log("変数が変わった!", { plus4, count });のように変数への代入を伴わなくとも、$: JavaScriptの文という構文で記述した文は、中で参照している変数が変更される度に実行されるようになります。

$: ... について

実は$: ...という構文は、SvelteがJavaScriptに新しく加えた構文ではありません。JavaScript標準の機能であるラベル付きの文 (opens new window)を流用し、$という名前のラベルが付いた文を特別扱いすることにしたのです。

動作例:

0 + 4 = 4

⚠️なお、plus4のような変数を定義する際、次のようにletを使用してもうまく動きません:

間違った例:

<script>
... 省略 ...
    let plus4 = count + 4;
... 省略 ...
</script>

... 省略 ...
1
2
3
4
5
6
7

Svelteにおけるlet [変数名] = ......に書く式は、あくまでも変数名の変数に対するデフォルトの値です。そのため、コンポーネントを作る時だけしか評価されません。なので、let plus4 = count + 4;と書いた場合はcountを更新してもplus4は更新されないのでご注意ください。

それから、$: let plus4 = ...と書いてもいけません。構文エラーになってしまいます:

エラーの例:

The keyword 'let' is reserved svelte(parse-error)

# 🚩ここまでにできたこと

  • コンポーネントにおいてletを使って定義した変数について、値を変える毎にHTML内で参照している箇所に変更を伝えることができた
  • <script>の中でも$: ...という構文を使うことで、letで定義した変数の変更に併せて文を実行できた

# その6 コンポーネントの分割

ここからは、ボタンを押した回数が「3の倍数のときだけアホになるボタン」というコンポーネントを、いくつかのステップに分けて作ってみましょう。まずは一つの.svelteファイルに書いていたコンポーネントを、別の.svelteファイルに分割する方法を紹介します。

コンポーネントを分割する方法を説明するため、ここまで編集したApp.svelteと同じディレクトリーに新しいファイルを作成してください。CodeSandboxの画面中央左の方にある、紙に折り目をつけたようなアイコン📄を押すと、新しいファイルができます:

ファイル名はNabeatsuButton.svelteにしましょう(最終的に「3の倍数のときだけアホになるボタン」にするので)。

ファイルができたら、内容は次のようにしてください:

NabeatsuButton.svelte:

<script>
    export let count;
</script>
<button>
{count}
</button>
1
2
3
4
5
6

export let count;という文は、新しい構文を使っています。Svelteにおいて、export let [変数名];で宣言した変数は「プロパティー」と見なされ、宣言した変数を含むコンポーネントを利用する側が設定できるようになります(具体的にどう設定するかは後ほど)。

つまり、上記のコンポーネントは、countというプロパティーを<button>タグで表示する、ただそれだけのものです。これに追々機能を追加して、「3の倍数のときだけアホになるボタン」を作っていきます。

次は、いよいよ新しく作ったコンポーネントNabeatsuButton.svelteを実際に使用しましょう:

App.svelte:

<script>
    import NabeatsuButton from './NabeatsuButton.svelte';
</script>
<NabeatsuButton count={3}/>
1
2
3
4

Svelteのコンポーネントを別のコンポーネントから参照する場合、普通のJavaScriptにおけるimport文を使用します。import [インポートする側で使用する名前] from '[インポートするファイルの名前]';という構文ですね(※1)。このように書いた上で、<[インポートする側で使用する名前]/>と書くと、該当の箇所がインポートしたコンポーネントのHTMLで置き換わります。[インポートする側で使用する名前]という名前の新しいHTMLタグが作成されるイメージで捉えてください(※2)。

注釈

(※1): インポートするファイル名における拡張子.svelteは特に付けなくてもいいようですが、慣習上付けることが多いようなのでこのドキュメントでも付けることにします。

(※2): このような構文のため、普通のHTMLのタグと区別できるよう、インポートしたコンポーネントの名前は必ず大文字で始めることになっています。ちなみにこれはSvelteだけでなく、ReactやVue.jsにおいても同様のルールがあります。

そして、<NabeatsuButton count={3}/>count={3}という箇所が、NabeatsuButtonコンポーネントのプロパティーを設定している箇所です。先ほどexport letしたcountという名前が出てきました!ちょうどHTMLタグで属性を設定するがごとく、[プロパティー名]=[値]という構文で、指定したコンポーネントのプロパティーを設定することができます。{3}のように、[値]の箇所で{... JavaScriptの式 ...}の構文を使っているのは、HTMLの属性値が本来文字列しかサポートしていない関係で、単にcount=3と書くと「3」という文字列が渡ってしまうからです。

動作例:

3

まだまだこれだけでは面白くないですね。どんどん次に行きましょう!

# その7 コンポーネントにイベントハンドラー

前のステップで作成したアプリケーションでは、ボタンを押しても何も起こりません。というわけで今度はNabeatsuButtonにイベントハンドラーを加えましょう:

NabeatsuButton.svelte:

<script>
    export let count;
    export let onClick;
</script>
<button on:click={onClick}>
{count}
</button>
1
2
3
4
5
6
7

以前のステップでApp.svelteに書いたincrementCountのような関数を直接NabeatsuButtonに定義してon:clickに設定してもよいのですが、ここでは敢えてonClickというプロパティーを追加することで、NabeatsuButtonを使用する側でイベントハンドラーを設定することを求めています。これが必ずしもよい設計、というわけではありませんが、コンポーネントの役割を分担する際の一例として参考にしてください。

と、言うわけで早速App.svelte側でもイベントハンドラーを設定しましょう。以前使ったincrementCountをそのまま利用します:

App.svelte:

<script>
    import NabeatsuButton from './NabeatsuButton.svelte';

    let count = 0;
    function incrementCount() {
        count += 1;
    }
</script>
<NabeatsuButton count={count} onClick={incrementCount}/>
1
2
3
4
5
6
7
8
9

NabeatsuButtonコンポーネントに渡しているプロパティーcountは、Appコンポーネントにおける変数countを参照しています。そのため、やはりAppの中でcountの値が変わった場合もNabeatsuButtonにその変更が伝わり、NabeatsuButtonが表示する値も変わるのです。

動作例:

0

# 🚩ここまでにできたこと

  • .svelteファイルを分けることで、コンポーネントを分割できた
  • コンポーネントの中でexport letすることでプロパティーを定義し、利用する側で具体的な値を設定できた

# その8 コンポーネントのロジック

そろそろ、「Nabeatu」らしく「3の倍数でアホになる」ロジックをNabeatsuButtonに加えましょう。なお「3が含まれる数字」という条件についてはやってみたい人だけがやってください。ここでは割愛します。

「アホになる」ためには表示するHTMLの内容でアホになったことを表すのが適当でしょう。従って「アホになる」場合とそうでない場合を、HTMLの中で分岐する必要がありますね。そこで出てくるのがSvelteの{#if [JavaScriptの式]} ... {:else} ... {/if}という構文です:

NabeatsuButton.svelte:

<script>
    export let count;
    export let onClick;

    $: isAho = count % 3 == 0;
</script>
<button on:click={onClick}>
{#if isAho}
    ((●˚⺣˚)&lt;{count}!!
{:else}
    (・∀・)&lt;{count}
{/if}
</button>
1
2
3
4
5
6
7
8
9
10
11
12
13

上記のように書き換えたNabeatsuButtonでは、isAho変数の値がtrueであるかfalseであるかによって、<button>タグで表示する内容を切り替えています。下記のifブロックという構文を使うことで、isAhotrueであれば((●˚⺣˚)<{count}!!falseであれば(・∀・)<{count}を表示します(<&lt;に置き換えられているのにご注意ください)。

{#if [JavaScriptの式]}
    [trueの場合に表示する内容]
{:else}
    [falseの場合に表示する内容]
{/if}
1
2
3
4
5

なお、どう「アホになる」かについては、[Python]12行で作る世界のナベアツプログラム|ねこぐらまー|note (opens new window)からアイディアを拝借しました。もちろん他の方法で「アホになる」のでも構いません。「ナベアツ プログラミング」で検索すれば他にも出てくるので参考にしてみるのもよいでしょう。

動作例:

((●˚⺣˚)<0!!

# 🚩ここまでにできたこと

  • {#if [JavaScriptの式]} ... {:else} ... {/if}という構文を使って、JavaScriptの値に応じてHTMLの中身を切り替えることができた

# その9 コンポーネントの中に閉じたstyle

せっかくHTMLを使っているのですから、「アホになる」時の見た目をもっといじってみましょう。見た目をいじる、といえばCSSです。Svelteのコンポーネントでは、<style>タグの中にCSSを記述すると、コンポーネントの中にしか影響しないCSSが書けます。

以下は「アホになる」時だけ斜字体にする例ですが、みなさんの好みでもっとド派手🤡な見た目にしてもよいでしょう:

NabeatsuButton.svelte:

<script>
    export let count;
    export let onClick;

    $: isAho = count % 3 == 0;
</script>
<style>
    .aho {
        font-style: italic;
    }
</style>
<button on:click={onClick} class={isAho ? "aho" : ""}>
{#if isAho}
    ((●˚⺣˚)&lt;{count}!!
{:else}
    (・∀・)&lt;{count}
{/if}
</button>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

上記の例ではahoというクラスを作成して、<style>ではahoクラスが充てられた要素の文字が斜字体(font-style: italic)になるよう設定しています。そして、isAhotrueの場合、つまりcountが3の倍数の時はbutton要素のclass属性にahoが設定することで、3の倍数の時だけ見た目が変わるようにしています。{...}の中には任意のJavaScriptの式が書けるので、上記では三項演算子を使ってclass属性の中身を記述しているんですね。

もちろん、下記のように書き換えても問題ありません:

... 省略 ...
{#if isAho}
<button on:click={onClick} class="aho">
    ((●˚⺣˚)&lt;{count}!!
</button>
{:else}
<button on:click={onClick}>
    (・∀・)&lt;{count}
</button>
{/if}
1
2
3
4
5
6
7
8
9
10

TIP

ここでは割愛しますが実際のところ、Svelteで要素のclass属性を設定する方法にはもっと簡潔な方法があります。詳細は公式のドキュメント (opens new window)をご覧ください。

動作例:

((●˚⺣˚)<0!!

さて、Svelteのコンポーネントにおける<style>の使い方はこれだけなんですが、実は他にも知っておくべき重要なポイントがあります。作成したNabeatsuButtonコンポーネントが持つ、<button>要素を右クリックして、Developer Toolsを起動してください。Chromeでは「検証」、Edgeでは「開発者ツールで調査する」、Firefoxでは「検証」をクリックすれば起動します。

すると、<button>要素のclass属性にahoの他svelte-1ot19pzのような、svelte-で始まる見慣れないクラスも付与されているのが分かるでしょう:

<button class="aho svelte-1ot19pz">

更にDeveloper Toolsの画面右、<button>要素に適用されているCSSのルールの一覧を見ると、私たちが<style>タグに書いた.ahoについてのルールが、.aho.svelte-1ot19pzのような、<button>に追加で適用された、svelte-で始まるクラスを付け加えたセレクターで置き換えられていることが分かるでしょう:

.aho.svelte-1ot19pz

この、Svelteが自動で書き換えるセレクターにより、Svelteのコンポーネントで<style>に書いたCSSは、そのコンポーネントだけで有効になります。このようにしてSvelteは、コンポーネントが外に与える影響を最小化しているんですね。

# 🚩ここまでにできたこと

  • Svelteのコンポーネントにおける<style>が、書いたコンポーネントだけに適用されることが分かった

# その10 input要素からの入力を渡す

このSvelteチュートリアルで勉強するSvelteの機能は、あと一つです。早速学びましょう... と言いたいところですが、その前にこれまでの復習を兼ねて、その機能を使わなかった場合にどれだけ煩雑になるかを確かめてみましょう。

例として、NabeatsuButtonに新しいプロパティー、divisorを追加して、何の倍数で「アホになる」かを設定できるようにしましょう。NabeatsuButtonがより多様なケースに対応できるようになりますね!

まずはdivisorプロパティーをNabeatsuButtonに追加した上で、これまで3の余りを計算していたところをdivisorの余りを計算するよう書き換えます:

NabeatsuButton.svelte:

<script>
    export let count;
    export let divisor;
    export let onClick;

    $: isAho = count % divisor == 0;
</script>
<style>
    .aho {
        font-style: italic;
    }
</style>
<button on:click={onClick} class={isAho ? "aho" : ""}>
{#if isAho}
    ((●˚⺣˚)&lt;{count}!!
{:else}
    (・∀・)&lt;{count}
{/if}
</button>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

それから、AppコンポーネントではNabeatsuButtonに渡すdivisorと、divisorを設定する<input>要素を作ります:

App.svelte:

<script>
    import NabeatsuButton from './NabeatsuButton.svelte';

    let count = 0;
    function incrementCount() {
        count += 1;
    }

    let divisor = 3;
    function updateDivisor(event) {
        divisor = Number(event.target.value);
    }
</script>
<label>
    何の倍数でアホになる?:
    <input type="number" value={divisor} on:change={updateDivisor}/>
</label>
<br />
<NabeatsuButton onClick={incrementCount} count={count} divisor={divisor}/>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

新しく追加した<input>要素では、value={divisor} on:change={updateDivisor}と設定することで、divisorの値を常に表示しつつchangeイベントのイベントハンドラーを設定し、divisorevent.target.value、つまり<input>に新しく入力された値をセットしています。

動作例:

何の倍数でアホになる? 0 ((●˚⺣˚)<0!!

# その11 input要素からの入力を渡す: もっと簡単な方法

前のステップでは、<input>要素のvalue属性で変数divisorを設定し、changeイベントでdivisorを更新する設定をしていました。このように、<input>要素に変数の値を表示して、<input>要素の内容が変わると変数にその値を反映させる、という変数の中身と<input>要素の値を相互に同期させる実装パターンは、とてもありふれています。なので、Svelteではこれを「双方向バインディング」という機能で簡単に実装できるようにしました。AngularやVueなど、他のフレームワークでも馴染み深い機能ではないかと思います。

「双方向バインディング」を使うとステップ10のAppコンポーネントは、次のように生まれ変わります:

App.svelte:

<script>
    import NabeatsuButton from './NabeatsuButton.svelte';

    let count = 0;
    function incrementCount() {
        count += 1;
    }

    let divisor = 3;
</script>
<label>
    何の倍数でアホになる?:
    <input type="number" bind:value={divisor}/>
</label>
<br />
<NabeatsuButton onClick={incrementCount} count={count} divisor={divisor}/>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

<input>要素にはbind:value={divisor}と書くだけでよくなり、updateDivisorのような<input>要素の値をdivisorにセットするだけの関数もなくなりました!bind:value={divisor}は、対象の<input>要素のvalue属性と変数divisorを双方向に紐付ける(バインドする)ための設定です。紐付けた変数divisorの値をvalue属性で表示して、ユーザーの入力によってvalue属性が更新されたらdivisorの値も更新し、また新たな値をvalue属性の値として表示する...という処理をこれ一つで賄ってくれます。

補足

更にSvelteは気を遣ってくれまして、type="number"<input>要素については、入力された値を自動で数値に変換した上で紐付けた変数に代入してくれます。

動作例:

※ステップ10と同じなので省略

# 🚩ここまでにできたこと

  • 「双方向バインディング」を使うことで、紐付けた変数と<input>要素のvalue属性の値を簡単に同期させることができた

# おわりに

Svelteの機能はまだまだたくさんあります。気になる方は、とてもうまく翻訳された日本語のチュートリアル (opens new window)を軽く通してみるといいでしょう。


CC BY-SA Licensed | Copyright (c) 2023, Internet Initiative Japan Inc.