はじめてのSvelte - 基礎から応用まで

December 03, 2021
執筆者
レビュー担当者

はじめてのSvelte - 基礎から応用まで

背景

JavaScriptのフロントエンド開発に関わる技術は移り変わりが目まぐるしく、日々新しいツールやフレームワークが誕生しています。Svelteも近年注目を集めているフロントエンドフレームワークの一つです。2021年のStackOverFlowの開発者サーベイでは、Svelteが開発者から最も愛されているツールとして選ばれました。本稿では、Svelteの基礎的な開発方法から、Storeを使った状態管理やアニメーションの適用方法など、応用までご紹介します。

目標

このチュートリアルを最後まで進めると、Svelteの基礎から応用までを学べるとともに、以下のようなタスク管理アプリを作成できます。

アプリ完成イメージ

Svelteとは?

Svelteは、ブラウザ上で動作するユーザーインターフェースを作成するためのオープンソースフレームワークです。

ReactやVue.jsとの違い

SvelteはReactVue.jsと同様に、ユーザーインターフェースを作成するためのコンポーネントベースのJavaScriptフレームワークです。

しかし、SvelteとReactやVue.jsには決定的な違いがあります。Svelteは仮想DOMを使用しません。SvelteはTypeScriptで書かれたコンパイラであり、ビルド時にJavaScriptをコンパイルするのに対し、ReactやVue.jsは実行時に仮想DOMを使ってアプリケーションコードを解釈します。

Svelteとほかのフレームワークの違いについて詳しくは、ブログ記事「SvelteとReactの基本を比較」を参照してください。

Svelteを使うメリット

Svelteを使うメリットは以下のとおりです。

仮想DOMを使用しない

Svelteの最大のメリットは、コンパイラであるため、仮想DOMを使用しないことです。DOMの差分確認をするアルゴリズムは効率的ですが、実行コストが高くなります。Svelteは、コードをバニラJavaScript (*1)にコンパイルするため、アプリケーションの高速化とパフォーマンスの向上が期待できます。

(*1)ライブラリやフレームワークを使わないでJavaScriptを書くこと。

軽量で高パフォーマンス

Svelteは、高度に最適化されたバニラJavaScriptを生成します。実行時のフレームワークのオーバーヘッドがほぼなくなり、バンドルサイズやメモリ使用量が最低限で済みます。このため、アプリケーションのロードと実行の高速化を実現できます。

真のリアクティブシステム

Svelteはリアクティブに作られています。Svelteは、仮想DOMなどの仲介者を必要とせずにDOMを直接変更するバニラJavaScriptを生成します。また、リアクティブなStoreがビルトインで装備されているので、状態管理専用ライブラリを使用する必要がありません。変数の値を変更するだけでUIに反映されます。

記述するコードを少なくできる

ライフサイクルフックなどのボイラープレートを記述する必要がなく、また状態管理専用ライブラリを使用する必要がないため、記述するコードがほかのフレームワークより少なくて済みます。これにより、コードの可読性を上げることができます。

アニメーションやエフェクトがビルトインで装備されている

Svelteにはアニメーションやエフェクトが組み込まれており、洗練されたUIでインタラクティブな動作を簡単に実装できます。

アプリケーションの構造

Svelteの概要がわかったところで、本稿で作成するアプリケーションの構造をご紹介します。

  • App: アプリケーションの実行エントリーポイントとなるルートコンポーネント。このコンポーネントでユーザーが氏名を入力する画面を表示します。
  • ToDoInputForm: タスクを新規追加する入力フォームのためのコンポーネント。
  • ToDoList: タスクの表示、チェックボックスとタスク削除機能のためのコンポーネント。

Appコンポーネント


     
ToDoInputForm/ToDoList コンポーネント

一般的に、このようなシンプルで小規模なアプリケーションでは3つもコンポーネントを準備する必要はなく、Appコンポーネントひとつで全ての動作をまかなうことが多いです。今回はコンポーネント間のデータ共有について学習するために、意図的に細かくコンポーネントを分けています。

必要なツール

Svelteプロジェクトを作成する

まずはSvelteプロジェクトを作成しましょう。degitと呼ばれるGitリポジトリをコピーするツールを使用してプロジェクトを作成します。

ターミナルを開き、任意のディレクトリで以下のコマンドを実行します。

npx degit sveltejs/template my-svelte-project
cd my-svelte-project

このコマンドで、my-svelte-projectフォルダを作成し、Svelteのデフォルトのプロジェクトテンプレートをフォルダ内にダウンロードします。cdコマンドで新しく作成したプロジェクトに移動します。

次に、プロジェクトで必要な依存パッケージをダウンロードします。以下のコマンドを実行します。

npm install

このコマンドで、package.jsonに含まれるdependenciesdevDependenciesnode_modules内にインストールします。

package.jsonを開くと、以下のようなパッケージが確認できます。

 

package.jsonに含まれているパッケージは、使用しているSvelteのバージョンにより異なります。

"devDependencies": {
  "@rollup/plugin-commonjs": "^17.0.0",
  "@rollup/plugin-node-resolve": "^11.0.0",
  "rollup": "^2.3.4",
  "rollup-plugin-css-only": "^3.1.0",
  "rollup-plugin-livereload": "^2.0.0",
  "rollup-plugin-svelte": "^7.0.0",
  "rollup-plugin-terser": "^7.0.0",
  "svelte": "^3.0.0"
},
"dependencies": {
  "sirv-cli": "^1.0.0"
}

ここで、Svelteがコンパイラーだということが改めて読み取れます。コンパイラーでは、アプリケーションコードはビルド時にバニラJavaScriptに変換されます。このため、ほとんどのパッケージは開発時に使用するパッケージであるdevDependenciesに格納されています。

devDependenciesに含まれるrollupは、Svelteで使用されるモジュールバンドラーです。rollupはWebpackのように、モジュール式のコードを、ブラウザが容易に解析してUIに表示できるようなファイルにまとめます。.svelteファイル、ファイルに含まれるモジュール、propsなどを、HTML、CSS、JavaScriptファイルに変換します。

degitでは、デフォルトでhttp://localhost:5000/が使用されます。macOS Monterey以降を使用している場合は、AirPlay Receiverがポート5000を使用しているので正しく動作しません。この問題を防ぐために、package.jsonstartスクリプトで別のポートを使用するように変更します。package.jsonstartスクリプトを以下のように変更します。

{
  "name": "svelte-app",
  "version": "1.0.0",
  "private": true,
  "scripts": {
    "build": "rollup -c",
    "dev": "rollup -c -w",
    "start": "sirv public --no-clear --port 8080"
  },
 ....省略
}

お使いの環境により、--portの後に来るポート番号を変更してください。

この例ではhttp://localhost:8080でアプリケーションにアクセスできます。

次に開発サーバーを起動します。ターミナルで以下のコマンドを実行します。

npm run dev

実行すると、以下のようなアウトプットがターミナルに表示されます。


アウトプット

アウトプットから、アプリケーションがポート8080で起動していることがわかります。http://localhost:8080にアクセスすると、以下のスタート画面が表示されます。

スタート画面

ここで一度ターミナルでアプリケーションを終了させておきます。

フォルダの構造

作成したプロジェクトのフォルダの構造を確認しましょう。

アプリケーションの構造
  • node_modules: package.jsonに記載されているdevDependenciesdependenciesが含まれています。
  • public: Svelteがコンパイル処理で出力したリソースが含まれるフォルダです。build/bundle.jsbuild/bundle.cssには最適化されたJavaScriptとCSSが含まれています。bundle.jsbundle.csspublic/index.htmlでインポートしています。
  • src: アプリケーションのコンポーネントを格納するフォルダ。アプリケーションのスタート地点となるmain.jsApp.svelteがデフォルトで含まれています。新規のコンポーネントをこのフォルダに格納します。

Svelteコンポーネントの仕組みをさらに詳しく解説します。まずはsrc/main.jsを見てみましょう。

import App from './App.svelte';

const app = new App({
    target: document.body,
    props: {
        name: 'world'
    }
});

export default app;

main.jsはSvelteアプリケーションのスタート地点です。アプリケーションのメインコンポーネントであるApp.svelteをインポートし、新しいAppインスタンスを作成しています。appで設定オブジェクトを作成し、targetpropsを渡しています。

targetdocument.bodyに設定されており、Appコンポーネントで生成されたHTMLをアプリケーションのdocument.bodyに格納することを指定しています。propsAppコンポーネントに渡すプロパティを含むオブジェクトを割り当てています。

本稿では、このprops: { name: 'world'}を使用しないので、オブジェクトごと削除してください。

次に、App.svelteを見てみましょう。

<script>
  export let name;
</script>

<main>
  <h1>Hello {name}!</h1>
  <p>Visit the <a href="https://svelte.dev/tutorial">Svelte tutorial</a> to learn how to build Svelte apps.</p>
</main>

<style>
  main {
    text-align: center;
    padding: 1em;
    max-width: 240px;
    margin: 0 auto;
  }
....省略
</style>

このように、Svelteのコンポーネントは<script>、HTMLテンプレート、<style>の3部分で構成されています。

<script>には、コンポーネントのJavaScriptコードを記述します。App.svelteでは、name変数をプロパティとしてエクスポートしています。本稿ではnameは使用しないので、後ほど削除します。

HTMLテンプレートには、コンポーネントのHTMLを生成するためのマークアップコードを含めます。

<style>にはコンポーネントにのみスコープされるCSSを含めます。

新しいコンポーネントを作成する

アプリケーションの構造が理解できたところで、新規のコンポーネントを作成しましょう。

srcフォルダ配下にToDoInputForm.svelteファイルを作成してください。

Propsと算出プロパティ

SPAでは、データを複数のコンポーネントで共有する際、親コンポーネントから子コンポーネントにデータを渡すことが一般的です。この導線はProps(Properties)と呼ばれるデータを宣言して実装します。

Svelteでは、<script>ブロック内でexportキーワードを使用して変数を宣言するだけで、データを他のコンポーネントに渡すことができます。本稿では、ToDoInputFormコンポーネントにuserName変数を追加し、ユーザー名を他のコンポーネントに共有できるようにします。

ToDoInputForm.svelteに以下のコードを追加します。

<script>
  export let userName
</script>

<h3>{userName}のタスクリスト:</h3>

ここでは、「{ユーザー名}のタスクリスト:」というテキストをヘッダーとして出力します。

このコンポーネントをアプリケーション上で表示するには、メインコンポーネントであるApp.svelteToDoInputForm.svelteコンポーネントを参照する必要があります。

App.svelteを以下のコードに変更します。

<script>
  import ToDoInputForm from "./ToDoInputForm.svelte"
</script>

<main>
  <ToDoInputForm userName="田中花子" />
</main>

<style>
  main {
    text-align: center;
    padding: 1em;
    max-width: 450px;
    margin: 0 auto;
  }
</style>

<script>ブロックとHTMLテンプレートでToDoInputFormコンポーネントを参照しています。<ToDoInputForm />タグをHTMLテンプレートに含めることにより、ToDoInputFormコンポーネントのHTMLがAppコンポーネントのHTMLに含まれるようにします。

Svelteでは算出プロパティもサポートされています。

App.svelte<script>ブロックとHTMLテンプレートの内容を以下のように変更します。

<script>
  import ToDoInputForm from "./ToDoInputForm.svelte"

  let lastName = "田中"
  let firstName = "花子"

  $: fullName = lastName  + " " + firstName
</script>

<main>
  <ToDoInputForm userName={fullName} />
</main>

算出プロパティはリアクティブです。lastNamefirstNameなどの算出プロパティに依存する変数が変更された場合、userNameの値が自動的に更新されます。

ファイルを保存し、npm run devを実行して再度アプリケーションを実行してください。

ブラウザで動作確認に使用したポート(例: http://localhost:8080)にアクセスすると、以下の画面が表示されます。

算出プロパティ

App.svelteで、<script>ブロック内のlastNamefirstNameの値を以下に変更し、ファイルを保存します。
let lastName = "山田"
let firstName = "太郎"

すると、算出プロパティが値の変更を検知し、以下のようにユーザー名が切り替わります。

算出プロパティ変更後

ロジックの追加

Svelteではテンプレートに直接ロジックを追加できます。

If-else

ユーザーが氏名を入力していなければ、氏名入力画面を表示し、入力したらタスクリストを表示する仕様をIf-elseで実装します。

まず、氏名入力後に表示する、タスクリストコンポーネントを作成します。srcフォルダ配下にToDoList.svelteファイルを作成してください。

ToDoList.svelteに以下のHTMLを追加し、ファイルを保存します。

<h2>ToDoListコンポーネント</h2>

ここではIf-elseロジックの効果がわかりやすいよう、一旦簡素なHTMLを適用しています。このHTMLは後ほど変更します。

App.svelte<script>ブロックとHTMLテンプレートの内容を以下のように変更します。

<script>
  import ToDoInputForm from "./ToDoInputForm.svelte"
  import ToDoList from "./ToDoList.svelte"
  let nameEntered = false

  let lastName = "山田"
  let firstName = "太郎"

  $: fullName = lastName  + " " + firstName
</script>

<main>
{#if nameEntered}
  <h1>タスクリストアプリケーションへようこそ、{fullName}さん!</h1>
  <ToDoInputForm userName={fullName} />
  <ToDoList />
{:else}
  <h1>タスクリストアプリケーションへようこそ!</h1>
  <h3>名前を入力してください。</h3>
  <div>
    <input type="text" placeholder="姓" required>
  </div>
  <div>
    <input type="text" placeholder="名" required>
  </div>
{/if}
</main>

ファイルを保存してください。

このコードでは、「ユーザーが氏名を入力したかどうか」という状態をnameEnteredで管理しています。ユーザーが氏名を入力したら、nameEnteredtrueになります。nameEnteredtrueの場合は名前の入力画面を表示し、falseであればタスクリストを表示します。このレスポンシブな動作を、{#if [条件]} ... {:else} ... {/if}で実装します。

また、if...elseに加えてelse ifも使用したい場合は、{:else if [条件]}で実装できます。詳しくは、Svelte 公式ドキュメントを参照してください。

ブラウザで動作確認に使用したポート(例: http://localhost:8080)を開くと、{:else}の条件が満たされ、氏名の入力画面が表示されます。

if...else 検証



フォーム送信機能は後ほど追加していきますが、この時点でif...elseが機能しているかを確認しましょう。

nameEnteredの値をtrueに変更し、ファイルを保存します。すると、{#if}の条件が満たされ、タスクリスト画面が表示されます。
ifの条件が満たされる


nameEnteredの値を再びfalseに戻し、ファイルを保存します。

双方向データバインディング

次に、データバインディングを導入します。通常、Svelteアプリケーションでは親から子へデータを渡すトップダウンの構造を取ります。しかし、フォームなど複数の方向からデータを更新したい場合もあります。このような変更で、データバインディングが便利です。データバインディングを使用すると、属性の値を設定し、その属性の変更を検知し反応するリスナーを設定できます。

このデータバインディングを、氏名入力フォームで使用します。App.svelteのHTMLテンプレートの<input>フォームを以下のように変更します。

<div>
  <input bind:value={lastName} type="text" placeholder="姓" required>
</div>
<div>
  <input bind:value={firstName} type="text" placeholder="名" required>
</div>

input要素に入力される値は、{lastName}{firstName}変数に紐付けられます。この紐付けにより、データを双方向で同期させることができます。

これまでユーザーの氏名は「山田太郎」をハードコードしていましたが、初期値を空文字列に設定し、ユーザーがフォームで入力した値でユーザー名を更新するようにします。

App.svelteの<script>ブロック内のlastNamefirstNameの値を以下のように変更します。

let lastName = ""
let firstName = ""

初期状態では、lastNamefirstNameの値が空文字で、ユーザーが入力要素の値を変更すると、それに応じて値が更新されるようになりました。

イベント

次に、Svelteでのイベントの実装方法を学習します。フォームでユーザーが氏名を入力後、入力内容を送信するためのボタンを実装します。

App.svelteのHTMLテンプレートの{:else}ブロックの内容を以下のように変更します。

<h1>タスクリストアプリケーションへようこそ!</h1>
<h3>名前を入力してください。</h3>
<form on:submit|preventDefault={handleSubmit}>
  <div>
    <input bind:value={lastName} type="text" placeholder="姓" required>
  </div>
  <div>
    <input bind:value={firstName} type="text" placeholder="名" required>
  </div>
  <button type="submit">タスク管理を始める</button>
</form>

ここでは、氏名入力後に入力内容を送信し、タスクリスト画面に進むためのボタンをクリックしたときのsubmitイベントを検知し、イベントに対しての反応を定義しています。

フォームにhandleSubmitイベントハンドラー関数とsubmitイベントを紐付けています。ユーザーが「タスク管理を始める」ボタンをクリックすると、handleSubmit関数が呼び出され、イベントのハンドリングが行われます。

また、フォームのブロック全体にon:submit|preventDefault={handleSubmit}修飾子を使用することにより、handleSubmitハンドラーが走る前にevent.preventDefault()を走らせます。

フォームはデフォルトの動作として、form要素に送信先が指定されていない場合、現在のURLに対してフォームの内容を送信します。現在のURLに対してフォームの送信が行われると、ページが自動的にリロードされてしまいます。この動作を防ぐために、event.preventDefault()を走らせます。

次に、handleSubmitイベントハンドラーを追加しましょう。

App.svelte<script>ブロックの最後の行に、以下のコードを追加します。

function handleSubmit() {
  if (lastName && firstName) {
    nameEntered = true
  }
}

このコードで、ボタンがクリックされ、姓と名が両方入力されていると、nameEnteredtrueになります。これにより、{#if nameEntered}の条件が満たされ、タスクリストが表示されます。

ファイルを保存し、ブラウザで動作確認に使用したポート(例: http://localhost:8080)を開きます。以下のような画面が表示されます。

イベント検証

氏名を入力し、「タスク管理を始める」をクリックします。

氏名を入力し、「タスク管理を始める」をクリック

画面がタスク管理画面に切り替わります。

画面がタスク管理画面に切り替わる

Each

データをループし、リストとして表示する場合は、{#each}を使います。

ToDoListコンポーネントに、タスクリストを表示します。ToDoList.svelteの内容を以下に変更します。

<script>
  let toDoItems = [
                    {text: 'ゴミ出し', status: true},
                    {text: 'プログラミング学習', status: false},
                    {text: '友達に連絡', status: false}
                  ];

  function removeFromList(i) {
    toDoItems.splice(i, 1)
    toDoItems = toDoItems
  }
</script>

{#each toDoItems as item, index}
  <div class="toDoItems">
    <input bind:checked={item.status} type="checkbox">
    <span class:checked={item.status}>{item.text}</span>
    <button on:click={() => removeFromList(index)}>削除</button>
    <br/>
  </div>
{/each} 

<style>
  .checked {
      text-decoration: line-through;
  }
  .toDoItems {
      text-align: left;
  }
</style>

ここでは、toDoItems配列を宣言し、textstatusのプロパティを含むオブジェクトを割り当てます。このデータを{#each}...{/each}ブロックを用いてリスト表示します。{#each}...{/each}の各要素が<li>要素としてHTMLに追加されます。

checkboxバインディングを使って、statusをチェックボックスが選択される度に切り替えます。

また、classディレクティブの省略版であるclass:クラス名={条件}の構文を使って、statusの値がtrueであれば、<span>要素のクラスがcheckedになり、ユーザーがチェックしたタスクに取り消し線のCSSが適用されるようにします。

on:clickremoveFromList関数を紐付け、タスクを削除できるようにします。

ファイルを保存し、ブラウザで動作確認に使用したポート(例: http://localhost:8080)を開きます。氏名を入力し、タスクリスト画面に移ると以下のような画面が表示されます。

タスクを削除できるようにする

これで、タスクのチェックと削除ができるようになりました。

Store

この状態だと、タスクの削除はできますが新規での追加ができません。ToDoInputForm.svelteにタスクの入力フォームを追加します。ToDoInputFormコンポーネントで入力されたタスクのデータをToDoListコンポーネントのタスクリストに追加するには、前述したPropsを利用できます。子コンポーネントAから親コンポーネントにデータを渡し、さらに親コンポーネントから別の子コンポーネントBに渡すことにより、兄弟コンポーネントにデータを共有できます。

ですが、アプリケーションが大きくなるにつれて、このような実装は複雑化しやすくなります。この問題を解決するには、Storeが有用です。

Storeは、状態が変更した時にsubscribeメソッドで状態を購読しているコンポーネントに一斉通知するためのオブジェクトです。

まず、状態を保存するためのファイルを作成します。/srcフォルダの配下にstore.jsを作成してください。

以下のコードをstore.jsにペーストしてください。

import { writable } from "svelte/store"

ここでは、svelte/storeモジュールから、書き込み可能なStoreを表すwritable関数をインポートしています。

次に、タスクリストのデータであるtoDoItemsstore.jsに移動し、各コンポーネントからtoDoItemsに購読するよう設定します。

store.jsを以下のように変更します。

import { writable } from "svelte/store"

export const toDoItems = writable([
  {text: 'ゴミ出し', status: true},
  {text: 'プログラミング学習', status: false},
  {text: '友達に連絡', status: false}
])

ToDoInputForm.svelteのコードを以下のように変更します。

<script>
  import { toDoItems } from "./store.js"

  export let userName
  let newItem = ""
  
  function addToList() {
    $toDoItems = [...$toDoItems, {text: newItem, status: false}]
    newItem = ""
  }
</script>
  
<input bind:value={newItem} type="text" placeholder="新しく追加するタスク">
<button on:click={addToList}>追加</button>

<h3>{userName}のタスクリスト:</h3>

ここでは、store.jsから状態のデータをインポートしています。また、新規のタスクを追加するためのフォームとボタンを提供しています。

ここで、$toDoItems = [...$toDoItems, {text: newItem, status: false}]に注目してみてください。SvelteのStoreでは、通常以下のようなsubscribeメソッドを使用して状態変更の通知を受け取ります。

count.subscribe(value => {
  count_value = value;
});

これに加えて、コンポーネントが破壊(destroyed)された際に、通知の購読を止める処理を導入する必要もあります。購読する状態が増えると、この構文を何度も繰り返すことになり、コードの量が増えていまします。ここで便利なのが、Auto subscriptionです。Auto subscriptionを使うと、$toDoItemsのように、状態の変数の前に$を付け加えるだけで、自動的に変数の状態変化の通知を受け取ることができます。

最後に、ToDoListコンポーネントにも状態を共有します。

ToDoList.svelteを以下に変更します。

<script>
  import { toDoItems } from "./store.js"

  function removeFromList(i) {
    $toDoItems.splice(i, 1)
    $toDoItems = $toDoItems
  }
</script>

{#each $toDoItems as item, index}
  <div class="toDoItems">
    <input bind:checked={item.status} type="checkbox">
    <span class:checked={item.status}>{item.text}</span>
    <button on:click={() => removeFromList(index)}>削除</button>
    <br/>
  </div>
{/each} 

<style>
  .checked {
      text-decoration: line-through;
  }
  .toDoItems {
      text-align: left;
  }
</style>

ここで行った変更は、<script>ブロック内でtoDoItems配列を定義する代わりにstore.jsから配列をインポートしたことと、HTMLテンプレートでtoDoItems$toDoItemsに変更したことのみです。

ファイルを保存し、ブラウザで動作確認に使用したポート(例: http://localhost:8080)を開きます。ユーザー名を入力し、タスクリスト画面に移ると以下のような画面が表示されます。

入力フォーム追加

入力フォームが新たに追加されました。

入力フォームにタスクを追加すると、

入力フォーム検証

以下のようにToDoList.svelteコンポーネントの部分にデータが共有されていることが確認できます。

新規の項目が追加される

ライフサイクル

Svelteを含むSPAフレームワークのコンポーネントにはonMountから始まり、onDestroyで終わるライフサイクルがあります。このライフサイクルの間で起こるイベントごとに様々な処理を行うことができます。

ライフサイクルのイベントで最もよく使われるのが、DOMのレンダリングが完了した直後に発生するonMountです。

このonMountイベントを使って、新規タスク入力フォームに自動的にフォーカスを合わせます。この機能を実装することにより、ユーザーが新規タスクを追加する際にフォームをクリックする必要がなくなります。

onMountを使うことにより、コンポーネントのレンダリングの完了直後にフォーカスを当てることができます。

ToDoInputForm.svelteを以下のコードに変更します。

<script>
  import { toDoItems } from "./store.js"
  import { onMount } from "svelte"

  export let userName
  let newItem = ""
  let newItemInputForm = null

  onMount(() => {
    newItemInputForm.focus()
  })
  
  function addToList() {
    $toDoItems = [...$toDoItems, {text: newItem, status: false}]
    newItem = ""
  }
</script>
  
<input bind:value={newItem} bind:this={newItemInputForm} type="text" placeholder="新しく追加するタスク">
<button on:click={addToList}>追加</button>

<h3>{userName}のタスクリスト:</h3>

SvelteでonMountを使用するには、svelteモジュールからonMount関数をインポートする必要があります。これを、<script>ブロックのimport { onMount } from "svelte"で実行しています。

newItemInputForm変数を宣言し、nullに初期設定します。

bind:thisディレクティブは、渡された変数に要素のDOMノードへの参照を設定します。このbind:thisを使って、<input>要素にnewItemInputFormを渡します。onMountはコンポーネントがレンダリングされた後に実行されるため、onMountメソッドが実行された時点ではすでに入力フォームが存在しています。このため、レンダリングが完了する前にnewItemInputFormには入力フォームが設定されており、フォームに対して即効でfocus()メソッドを実行できます。

ファイルを保存し、ブラウザで動作確認に使用したポート(例: http://localhost:8080)を開きます。ユーザー名を入力し、タスクリスト画面に移ると以下のような画面が表示されます。

onMountで入力フォームがハイライトされる

入力フォームに青色のフォーカスが掛かっていることが確認できます。

アニメーション

最後に、アニメーション機能をご紹介します。Svelteにはデフォルトでアニメーション機能が備わっており、専用パッケージやプラグインをダウンロードする必要がありません。これにより、美しいアニメーションを、アプリケーションのサイズを肥大させずに導入することが可能です。

本稿では、ふわっと要素を出したり消したりする「フェード(fade)」と、横方向から要素を出したり消したりする「スケール(scale)」アニメーションに着目します。

そのほかにSvelteにビルトインされているアニメーションについて詳しくは、Svelte公式ドキュメントを参照してください。

フェードとスケールを使って、タスクリストに項目を追加・削除する動作にトランジションを追加します。

ToDoListコンポーネントの<script>ブロックとHTMLテンプレートを以下に変更します。

<script>
  import { toDoItems } from "./store.js"
  import { fade, scale } from "svelte/transition"

  function removeFromList(i) {
    $toDoItems.splice(i, 1)
    $toDoItems = $toDoItems
  }
</script>

{#each $toDoItems as item, index}
  <div class="toDoItems" in:scale out:fade="{{ duration: 500 }}">
    <input bind:checked={item.status} type="checkbox">
    <span class:checked={item.status}>{item.text}</span>
    <button on:click={() => removeFromList(index)}>削除</button>
    <br/>
  </div>
{/each}

ここでは、Svelteモジュールからtransitionフォルダに含まれるfadescale関数をインポートしています。

また、タスクリストに含まれる全ての項目に対してトランジションを適用しています。Svelteでは、トランジションを追加する際にtransitionディレクティブを使いますが、ここではその短縮系のinoutを使用しています。要素が追加されたときにはinに指定されているscaleトランジションが、削除されたときにはoutに指定されているfadeトランジションが実行されます。

durationでトランジションの速度を500msに設定しています。

ファイルを保存し、ブラウザで動作確認に使用したポート(例: http://localhost:8080)を開きます。ユーザー名を入力し、タスクリスト画面に移り、タスクリストの項目を削除したり、追加したりすると以下のようなトランジションが実行されることが確認できます。

完成したアプリ

これでタスク管理アプリケーションが完成しました。このチュートリアルに出てきたコードはGithubリポジトリでご確認いただけます。

まとめ

いかがだったでしょうか。ReactやVue.jsなどのウェブ開発フレームワークやライブラリと比較して、SvelteはJavaScriptベースの新しいアプローチを提供しています。Svelteはコンパイラーとして動作することで、高度に最適化されたJavaScript、HTML、CSSコードを生成します。

Svelteは学習も比較的容易で、ソースコードも非常に読みやすいです。初めてSvelteを試してみた方も短時間で高度なアプリケーションを作成できます。ぜひこのチュートリアルをもとに、もっと踏み込んだSvelteアプリケーション開発に挑戦してみてください。

Twilio Blogに投稿してみたい方や、フィードバック、登壇、勉強会のお誘いなど気軽にsnakajima[at]twilio.comまでご連絡ください。開発中のプロジェクトに関してはGithub(smwilk)を覗いてみて下さい。