【Svelte3入門】ToDoリストをチュートリアルと照らし合わせて作ろう！

# はじめに！

React や Vue よりも早いと言われている Svelte を使って簡単なアプリを作っていきます。

この記事は実際に私が ToDoリストを作った順番に、チュートリアルを逆引きしながら作った実録でもあるので、読むだけでも開発体験をトレースできるんじゃないかと、淡く期待しています :blush:

React も Vue も好きな筆者が Svelte を触った感想は「**...めっちゃ分かりやすいがぁ!!** :rage1:」というもの。この開発体験を少しでも多くの方に共有したくてこの記事を書きます。

※[Svelte日本のコミュニティによって日本語のドキュメントも整備されています :+1:](https://svelte.jp/)

## Svelte って何？

私も数週間前までその存在を知りませんでした。
職場のフレンズたちが Svelte で盛り上がっているとき、私は冷めた目をしていました。
でも、知らないものを批判しちゃいかんと思い直し、まず調べたのがこちら。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/134221/4bb6a363-5963-4c7c-5d70-44d444ec856e.png)

Svelte が[ホームページ](https://svelte.jp/)で謳っていることは次の３つ...

1. **Write less code**: より少ないコードで
2. **No virtual DOM**: 仮想DOMを使わずに
3. **Truely reactive**: 真のリアクティブ

順に説明しますね。

### 1. Write less code: より少ないコードで 
まずは以下の動画をみてください。
![calc.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/134221/dfe0433b-0996-a999-04ca-37db05f42c86.gif)

単純な計算機能ですが、これを **Svelte は React と比べて32％、Vue と比べて55% のコード量**で実装できます。 

- 442文字: React
- 263文字: Vue
- **145文字: Svelte**

### 2. No virtual DOM: 仮想DOMを使わずに 

これは大事なポイントですが、 **Svelte はコンパイラー**であり、React や Vue のようなフロントエンドフレームワークではありません。これにより、**クライアントへ渡されるのは純粋な HTML と javascript と css** になり、余分なオーバーヘッドを無くして高速に動きます。余分なオーバーヘッドとはフレームワーク自体の転送容量や、仮想DOMの生成時間、その使用メモリを指します。

### 3. Truely reactive: 真のリアクティブ 

Svelte では 1と2のおかげで React / Vue のように複雑な状態管理を意識せずに書けます。実際、[チュートリアル](https://svelte.jp/tutorial/basics)を幾つかやるだけで、その簡潔な書き方におったまげるでしょう。

参考までに、@so99ynoodles さんの記事「[ReactとVueを改善したSvelteというライブラリーについて](https://qiita.com/so99ynoodles/items/a8144ced1a21467c6300)」によると

> Svelteは速く、軽いです。
> ベンチマークでReactの35倍、Vueの50倍速いです。
> Svelteはコンパイラーであるため、実質ライブラリーとしての容量は0kbです。

とのこと:open_mouth:

# 開発環境を作ろう

前置きはここまでにして、さっそく開発を始めていきましょう。

- 私の開発環境はこちらです。
  - mscOS Monterey
  - Visual Studio Code
  - npx v8.1.2

## Svelte プロジェクトを作成しよう

開発環境は公式リファレンスに記載の通りです。
まずは、以下のコマンドを実行してください。

```bash:プロジェクトの作成と実行
npx degit sveltejs/template todolist
cd todolist
npm install
```

そしたら、こんなプロジェクトができているので、、、
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/134221/e96baf5b-1764-ff6d-3ad7-5cbfb8aafee2.png)
さっそく動かしてみましょう。
`npm run dev`をコンソールで実行するとローカルホストが起動するので 
`http://localhost:8080/` にブラウザでアクセスしてください。
下記のように表示されたら成功。あなたの Svelte 生活の始まりですっ:rocket:

![localhost_8080_.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/134221/a564834a-f6f6-65ac-af60-e68a33fdeb5e.png)


## 実装に入る前に...Svelte の拡張機能を入れておこう

VSCode で開発するようなら、以下の拡張機能を入れておくとインテリセンスやシンタックスハイライト、`prettier`のコードフォーマットが効き、開発が捗るでしょう。

### [Svelte for VS Code](https://marketplace.visualstudio.com/items?itemName=svelte.svelte-vscode)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/134221/86c2a2a3-6ab9-d316-18c3-3dbc482dba18.png)

# ToDoリストの大枠を組み立てよう

ToDoリストに求める機能はそれほど多くないので、大枠からざっくり作っていこうと思いました。
（逆に大きな機能のときは、それらを構成する機能をテスタブルに作り、終盤でつないでいくことが多いです。）
ということで、まず手始めに知っておいてほしいことは...

## 基本構成、それは Script、HTML、そして Style

```html:/src/App.svelte
<!-- 自動生成された元の内容はガバっと変えていいよ！ -->
<!-- Script -->
<script>
  // 後で記述するよ
</script>

<!-- HTML -->
<div>
  絞り込み:
  <button>すべて</button>
  <button>未完了</button>
  <button>完了</button>
</div>
<div>
  <input type="text">
  <button>タスク追加</button>
</div>
<div>
  <ul>
    <li>
      <input type="checkbox"> レストランを予約する
    </li>
    <li>
      <input type="checkbox"> サプライズ用の指輪を買う
    </li>
    <li>
      <input type="checkbox"> フラッシュモブダンスを練習する
    </li>
  </ul>
</div>

<!-- Style -->
<style>
  /* TODO: CSS得意なフレンズに頼む */
  /* https://svelte.jp/tutorial/styling */
</style>
```

ここまでは特に何も特別なことはありません。

さて、上記の実装をしたら、こんなシンプルでハッピーなToDoリストの雛形ができます:fork_and_knife::ring::dancers:

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/134221/24587137-e55c-2c79-3c8b-fb629e5ff15e.png)


# Svelte を使ってToDoリストに機能をつける

大枠が完成したところで、以下の手順で機能を実装していきたいと思います。

1. タスクの直書きを配列に変更する
2. 「タスク追加」ボタンでタスクを追加する
3. 「絞り込み」機能で表示対象を切り替える

## 1. タスクの直書きを配列に変更する

実際のシステムであればサーバからデータを取得するのですが、
それは一旦置いといて、サクッと小さく動くものを作りたいと思いました。

```diff:/src/App.svelte
<script>
-  // 後で記述するよ
+  let todoList = [
+    { id: 0, done: false, title: 'レストランを予約する'},
+    { id: 1, done: false, title: 'サプライズ用の指輪を買う'},
+    { id: 2, done: false, title: 'フラッシュモブダンスを練習する'},
+  ]
</script>

...

  <ul>
-    <li>
-      <input type="checkbox"> レストランを予約する
-    </li>
-    <li>
-      <input type="checkbox"> サプライズ用の指輪を買う
-    </li>
-    <li>
-      <input type="checkbox"> フラッシュモブダンスを練習する
-    </li>
+    {#each todoList as todo (todo.id)}
+      <li>
+        <input type="checkbox" bind:checked={todo.done}> {todo.title}
+      </li>
+    {/each}
  </ul>
```

ここでは、以下のチュートリアルが役に立ちます。

- [Binding / Text inputs](https://svelte.jp/tutorial/text-inputs): `{todo.title}` 変数の値を表示してみよう。
- [Binding / Checkbox inputs](https://svelte.jp/tutorial/checkbox-inputs): `{todo.done}` 変数の値と checkbox の値を紐付けてみよう。
- [Logic / Keyed each blocks](https://svelte.jp/tutorial/keyed-each-blocks): ループ処理をしてみよう。`#each`句で各ToDoタスクと`(todo.id)`を紐付けることで、Svelte に変化を検知させることができるよ。

動かしてみると、先程と表示は変わりませんが、配列からToDoリストを表示するように変更できました。

## 2. 「タスク追加」ボタンでタスクを追加する

次に「タスク追加」ボタンに対してクリックイベントを追加していきたい。（自分に負けない！）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/134221/3c2afce2-2545-0b83-3eab-dcb1aa8d7b96.png)

```diff:/src/App.svelte
<script>
...

+  let title = ''
+
+  function add() {
+    todoList = [
+      ...todoList,
+      {
+        id: todoList.length,
+        done: false,
+        title,
+      },
+    ];
+  }
</script>

...

<div>
-  <input type="text">
-  <button>タスク追加</button>
+  <input type="text" bind:value={title}>
+  <button on:click={() => add()}>タスク追加</button>
</div>

・・・
```

ここでは、以下のチュートリアルが役に立ちます。

- [Binding / Text inputs](https://svelte.jp/tutorial/text-inputs): ←前述と同じ。input 要素に入力した値と title 変数を紐付けてみよう。
- [Reactivity / Assignments](https://svelte.jp/tutorial/reactive-assignments): button 要素のクリックイベントとjs関数を紐付けてみよう。


## 3. 「絞り込み」機能で表示対象を切り替える

さぁ、最後の工程です！ フィルタリングして見たい情報に絞り込んでやろうではありませんかっ！
（おーっ！！！！！）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/134221/9c230fb7-1345-b887-a8ef-93eef04e1692.png)

```diff:/src/App.svelte
<script>
...

+  let condition = null;
+
+  $: filteredTodoList = (todoList, condition) => {
+    return condition === null
+      ? todoList
+      : todoList.filter((t) => t.done === condition);
+  };
</script>

<div>
  絞り込み:
-  <button>すべて</button>
-  <button>未完了</button>
-  <button>完了</button>
+  <button on:click={() => { condition = null }}>すべて</button>
+  <button on:click={() => { condition = false }}>未完了</button>
+  <button on:click={() => { condition = true }}>完了</button>
</div>

-   {#each todoList as todo (todo.id)}
+   {#each filteredTodoList(todoList, condition) as todo (todo.id)}
      <li>
        <input type="checkbox" bind:checked={todo.done}> {todo.title}
      </li>
    {/each}

```

ここでは、以下のチュートリアルが役に立ちます。

- [Reactivity / Declarations](https://svelte.jp/tutorial/reactive-declarations): 変数の値を監視して動的に差分変更してみよう。

ここまでできたらToDoも完成です！
さぁ！みなさんもToDoを追加していきましょう！！:movie_camera:

![ezgif.com-video-to-gif.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/134221/d9430d53-614f-cd08-7dea-16ab99f9bf60.gif)

# ちょっと待った！ 何かおかしいぞ...

さっきの動画で何かマズい点あったの気づきました？
~~そうです。そもそも恋人がいないんですよ！~~
そうです。動かして分かったんですが、このToDoリストには具合が悪い点がいくつかあったんです。それは...

1. 初期カーソルが タスク入力欄にフォーカスされてないから入力しづらい
2. 「タスク追加」ボタンをクリックした後にタスク入力欄がクリアされない

そこで、最後のお仕置きをしたいと思いました。

## 初期化処理で操作性を改善する

上記の問題は初期化処理を追加することで解決しました。
では、さっそく...

```diff:/src/App.svelte
<script>
+  import { onMount } from "svelte";
+
+  onMount(() => {
+    init();
+  });
+
+  let initFocus = null;
+
+  function init() {
+    title = "";
+    initFocus.focus();
+  }

...

  function add() {
    todoList = [
      ...todoList,
      {
        id: todoList.length,
        done: false,
        title,
      },
    ];
+   init();
  }

...
</script>

...

<div>
- <input type="text" bind:value={title}>
+ <input type="text" bind:value={title} bind:this={initFocus}>
  <button on:click={add}>タスク追加</button>
</div>

...
```

ここでは、以下のチュートリアルが役に立ちます。

- [Lifecycle / onMount](https://svelte.jp/tutorial/onmount): コンポーネントのライフサイクルを理解しましょう。コンポーネントが最初にDOM描画された後に onMountハンドラで書いた処理を実行するよ。
- [Bindings / This](https://svelte.jp/tutorial/bind-this): `bind:this={変数}` で何でも好きな要素と紐付けてみよう。


### 完成版

```html:/src/App.svelte（完成版）
<!-- Script -->
<script>
  import { onMount } from "svelte";

  onMount(() => {
    init();
  });

  let initFocus = null;

  function init() {
    title = "";
    initFocus.focus();
  }

  let todoList = [
    { id: 0, done: false, title: "レストランを予約する" },
    { id: 1, done: false, title: "サプライズ用の指輪を買う" },
    { id: 2, done: false, title: "フラッシュモブダンスを練習する" },
  ];

  let title = "";
  function add() {
    todoList = [
      ...todoList,
      {
        id: todoList.length,
        done: false,
        title,
      },
    ];
    init();
  }

  let condition = null;

  $: filteredTodoList = (todoList, condition) => {
    return condition === null
      ? todoList
      : todoList.filter((t) => t.done === condition);
  };
</script>

<!-- HTML -->
<div>
  絞り込み:
  <button
    on:click={() => {
      condition = null;
    }}>すべて</button
  >
  <button
    on:click={() => {
      condition = false;
    }}>未完了</button
  >
  <button
    on:click={() => {
      condition = true;
    }}>完了</button
  >
</div>
<div>
  <input type="text" bind:value={title} bind:this={initFocus} />
  <button on:click={() => add()}>タスク追加</button>
</div>
<div>
  <ul>
    {#each filteredTodoList(todoList, condition) as todo (todo.id)}
      <li>
        <input type="checkbox" bind:checked={todo.done} />
        {todo.title}
      </li>
    {/each}
  </ul>
</div>

<!-- Style -->
<style>
  /* TODO: CSS得意なフレンズに頼む */
  /* https://svelte.dev/tutorial/styling */
</style>
```

完成:tada:
![todolist.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/134221/99c25d78-8ccb-e3f8-9126-f7c92b3f8f85.gif)

# 最後に、Svelte はいいぞ

コード量が少ないから見晴らしがよくて、とっつきやすいんじゃないかなと思います:two_hearts:
Svelte でもコンポーネント管理できるし、[Storybook for Svelte](https://storybook.js.org/docs/guides/guide-svelte/)もあるのでデザインシステムも作れる。そのうえ、SSRとして[SvelteKit](https://kit.svelte.jp/) という Next/Nuxt のようなフレームワークも用意されている。React Native / Vue Native よろしく [Svelte Naitve](https://svelte-native.technology/)というのもある。~~（誰だ！やり過ぎって言ったやつ！ 俺も同感だよっ！）~~
React / Vue を触ったことがある方にはすごく馴染みやすい技術だと思うので、試してみてもらえると嬉しいです！

また、昨日の @oekazuma の記事「[君はVue,Reactの次に来るSvelteを知っているか？](https://qiita.com/oekazuma/private/d3d97f78466be983b19f)」も併せて読んでいただけると嬉しいです:relaxed:

それでは、明日は @fussy113 の「[1ヶ月〇〇○円で速度改善！？事業でも個人開発でも導入できる画像リサイズのAPI](https://qiita.com/fussy113/items/c897612d784e3fbd707b)」です。どうぞご期待ください:satisfied: