【Svelte】ToDoアプリでVueとSvelteを比較する

# はじめに
初カキコ…ども…
[RUNTEQ Advent Calendar 2022](https://qiita.com/advent-calendar/2022/runteq) 5日目の記事となります。

最近ちらほらと噂で耳にするSvelte・・・
[The State of JS 2021](https://2021.stateofjs.com/ja-JP/libraries/front-end-frameworks)においても、ReactやVueを抑えてSvelteの方が満足度が高いとの結果が出ています。
「ReactやVueを必死にキャッチアップしたけど、また流行り変わっちゃうかもしれないの〜！？」と、
ゲンナリする気持ちが少し出そうですね。

日本でも流行るかどうか現状ではわかりませんが、
いざ実務で使うってなった時に「あ、Svelteっすね、楽勝です」と大見得を切れるように、
先に触れて楽しく遊んでおくのがこの世界で生き残るコツかもしれない。

そんな思いで本記事を作成しています。

# そもそもSvelteって？
公式によると以下の3つが特徴のようです。
- **Write less code（コード量を減らす）**
- **No virtual DOM（仮想DOMを使用しない）**
- **Truly reactive（真にリアクティブ）**

OKです、OKです、なんとなく凄そうですね。
もう少し詳しく見ていくために、
いま流行りのChatGPT先生に聞いてみましょう。

![スクリーンショット 2022-12-04 15.40.42.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2446718/4acdf44f-f0c6-0cae-31f2-e1ba9bf154b4.png)

いや〜〜〜、、、
予想以上にそれっぽい文章が出てきました。コレすごいですね。
とりあえず、キモとなる部分は、 **Svelteはコンパイラである** という点のようです。
参考：[Svelteの根本部分を簡単に理解する](https://qiita.com/k-penguin-sato/items/3a85f89bf193b63ed740)

## この記事でやること
さきほど紹介した特徴の一つである、**Write less code（コード量を減らす）** について、
VueのTodoアプリをSvelteに作りかえていくことで比較してみようと思います。

題材となるVueのアプリはCodeSandBoxで探してきた以下のアプリを使わせてもらいます。
Vue3系ですが、Composition APIは使っていません。
[Vue 3 Todo App](https://codesandbox.io/s/ooeo2?file=/src/App.vue)

Svelteで作り変えた完成形はこちら。
[Svelte_TODO](https://codesandbox.io/s/svelte-todo-76fsh7?file=/App.svelte)

CodeSandBoxにて公開しているので、気軽にいじってみてください。

## キャッチアップ方法
公式の[Tutorial](https://svelte.jp/tutorial/basics)を６章くらいまでさらっと行いました。(所要時間2h)
REPL上で学べてとんでもなく優秀なチュートリアルでした！楽しい！
簡単なTodoアプリなので、以降はチュートリアルを逆引しつつ作成しました。

# さっそく作ってみた
まずは完成形から。style部分については共通なので省略してます。
Vueと同じ挙動になるように忠実に作りかえたので、追加の機能などは入れていません。
![画面収録 2022-12-04 18.41.00.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2446718/44031225-c72b-4606-bfa9-b3aab0ef9474.gif)


<details>
<summary>Vueのコード</summary>

```js
<script>
class Todo {
  constructor(title, isCompleted) {
    this.isCompleted = isCompleted;
    this.title = title;
  }
}

export default {
  name: "App",
  data() {
    return {
      todos: [],
      todo: "",
    };
  },
  methods: {
    addToList: function () {
      let newTodo = new Todo(this.todo, false);
      this.todos.push(newTodo);
      this.todo = "";
    },
    removeFromList: function (index) {
      this.todos.splice(index, 1);
    },
  },
  components: {},
};
</script>

<template>
  <div class="container">
    <div class="title">
      <h1>Todos</h1>
    </div>
    <div class="todo-list-container">
      <ul v-if="todos.length > 0">
        <li v-for="(todo, index) in todos" :key="index">
          <p>{{ todo.title }}</p>
          <input type="checkbox" v-model="todo.isCompleted" />
          <button @click.prevent="removeFromList(index)">Delete</button>
        </li>
      </ul>
    </div>
    <div class="form-container">
      <form>
        <label for="todo">New Todo</label>
        <input type="text" v-model="todo" />
        <button type="submit" @click.prevent="addToList">Submit</button>
      </form>
    </div>
  </div>
</template>
```
</details>

<details>
<summary>Svelteのコード</summary>

```js
<script>
  let todo = { title: "", isCompleted: false };
  let todos = [];

  function addToList() {
    // todoListにtodoを追加する
    todos = [
      ...todos,
      {
        title: todo.title,
        isCompleted: false
      }
    ];
    // todoを初期化する
    todo = { title: "", isCompleted: false };
  }

  function removeFromList(index) {
    // todoListからtodoを削除する
    todos = todos.filter((_, i) => i !== index);
  }
</script>

<main>
  <div class="container">
    <div class="title">
      <h1>Todos</h1>
    </div>

    <div class="todo-list-container">
      {#if todos.length > 0}
      <ul>
        {#each todos as { title, isCompleted }, index}
          <li>
            <p>{index + 1 }：{title}</p> 
            <input type="checkbox" bind:checked={isCompleted} />
            <button on:click={() => removeFromList(index)}>Delete</button>
          </li>
        {/each}
      </ul>
      {/if}
    </div>

    <div class="form-container">
      <form>
        <label for="todo">New ToDo</label>
        <input type="title" bind:value={todo.title} />
        <button type="submit" on:click|preventDefault={addToList}>Submit</button>
      </form>
    </div>
  </div>
</main>
```
</details>

## ポイント解説

- ### フォーム操作

```js
<script>
  let todo = { title: "", isCompleted: false };
</script>

<input type="text" bind:value={todo.title} />
<input type="checkbox" bind:checked={todo.isCompleted} />

<p>{todo.title}</p>
<p>{todo.isCompleted}</p>

```

これだけで以下の挙動が実現できます。
`bind:value`などのイベント記述の部分はVueに似ていますが、変数定義などはReactっぽい感じでしょうか。
シンプルですね。
![画面収録 2022-12-04 18.53.47.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2446718/a91c015b-50d6-330a-8079-7ea2723a533f.gif)

---
- ### 配列への追加

```js
<script>
  let todo = { title: "", isCompleted: false };
  let todos = [];

  function addToList() {
    // todoListにtodoを追加する
    todos = [...todos,
      {
        title: todo.title,
        isCompleted: false
      }
    ];
    // todoを初期化する
    todo = { title: "", isCompleted: false };
  }
</script>

<form>
  <input type="text" bind:value={todo.title} />
  <button type="submit" on:click|preventDefault={addToList}>Submit</button>  
</form>

<ul>
  {#each todos as { title, isCompleted }}
    <li>
      <p>{title}</p> 
      <input type="checkbox" bind:checked={isCompleted} />
    </li>
  {/each}
</ul>
```

まず、`addToList`関数を呼び出す際に、`form`を利用しているので、イベント修飾子をつけています。
この辺の書き心地はvueと変わりないですね。
参考：[Vue.jsイベント修飾子.stopと.preventの使いどころ](https://techblog.roxx.co.jp/entry/2019/02/08/122914)
参考：[チュートリアル@event-modifiers](https://svelte.jp/tutorial/event-modifiers)
```js
// svelteのコード
<button type="submit" on:click|preventDefault={addToList}>Submit</button>

// vueのコード
<button type="submit" @click.prevent="addToList">Submit</button>
```

`addToList`関数は、ES6 のスプレッド構文を使用しています。
Svelteのリアクティビティは代入によってトリガーされます。
参考：[チュートリアル@updating-arrays-and-objects](https://svelte.jp/tutorial/updating-arrays-and-objects)
```js
// svelteのコード
let todo = { title: "", isCompleted: false };
let todos = [];

function addToList() {
  // todoListにtodoを追加する
  todos = [...todos,
    {
      title: todo.title,
      isCompleted: false
    }
  ];
  // todoを初期化する
  todo = { title: "", isCompleted: false };
}

// vueのコード
data() {
  return {
    todos: [],
    todo: "",
  };
},
methods: {
  addToList: function () {
    let newTodo = new Todo(this.todo, false);
    this.todos.push(newTodo);
    this.todo = "";
  }
}
```

html部分は、`each`ブロックで配列をまわしてます。
分割代入の仕方なども特段難しいところはありません。
Vueと比較したときに、Svelteの方が可読性は高い気がしました。
参考：[チュートリアル@each-blocks](https://svelte.jp/tutorial/each-blocks)
```js
// svelteのコード
{#each todos as { title, isCompleted }}
  <li>
    <p>{title}</p> 
    <input type="checkbox" bind:checked={isCompleted} />
  </li>
{/each}

// vueのコード
<li v-for="(todo, index) in todos" :key="index">
  <p>{{ todo.title }}</p>
  <input type="checkbox" v-model="todo.isCompleted" />
</li>
```
ここまでの挙動
![画面収録 2022-12-04 19.51.33.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2446718/f6351a31-59d6-bf79-aca4-daee7e70c580.gif)

---
- ### 配列から削除

```js
<script>
   // ・・・（中略）・・・

  function removeFromList(index) {
    // todoListからtodoを削除する
    todos = todos.filter((_, i) => i !== index);
  }
</script>

 // ・・・（中略）・・・
{#each todos as { title, isCompleted }, i}
    <li>
      <p>{title}</p> 
      <input type="checkbox" bind:checked={isCompleted} />
      <button on:click={() => removeFromList(i)}>Delete</button>
    </li>
{/each}
```
まずは`removeFromList`関数の呼び出し。
この辺りはvueとほとんど同じです。

```js
// svelteのコード
<button on:click={() => removeFromList(i)}>Delete</button>

// vueのコード
<button @click="() => removeFromList(index)">Delete</button>
```

配列から削除する関数は以下です。
indexを渡して`filter`で配列から取り除くだけです。

```js
// svelteのコード
function removeFromList(index) {
  todos = todos.filter((_, i) => i !== index);
}

// vueのコード
removeFromList: function (index) {
  this.todos.splice(index, 1);
}
```
ちなみにですが、元のvueのコードは`splice`を使って配列から取り除いており、svelteで行おうとすると以下のようになります。
svelte のリアクティビティは代入によってトリガーされ、配列やオブジェクトを変更するようなメソッドでは、その更新がトリガーされないためです。
spliceは破壊的に取り除くけど、 filterなら非破壊で新しい配列を作ってくれます。
```js
function removeFromList(index) {
  todos.splice(index, 1);
  todos = todos;
}

// もしくは以下
function removeFromList(index) {
  todos = [...todos.slice(0, index), ...todos.slice(index + 1)];
}

// 以下では挙動がおかしくなる
function removeFromList(index) {
  todos = todos.splice(index, 1);
}
```

## ざっくりと所感
今回は、簡単なTodoアプリだったので、コンポーネントの分割など一切行っていませんが、
基本的な書き心地はVueにかなり似ていて理解しやすかったです。

チュートリアルの分かりやすさと相待ってVueを学んでいた方はすんなりキャッチアップできると思います。
今回使っていない機能が多々ありますが、Svelteならではのオモシロ部分は以下の記事が分かりやすかったです。
参考：[Vueユーザーが感じたSvelteのおもしろい機能を紹介する](https://tech.smartcamp.co.jp/entry/svelte-feature-for-vuejs-engineer)

とはいえ、JSをしっかりと学べばいきなりSvelteから入っても大丈夫だと思うので、
駆け出しエンジニアからチャレンジしてみるのもいいかもしれません。

今回の目的である**Write less code（コード量を減らす）** に関して言えば、
Vue3の`CompotisionAPI`を利用するとかなりスッキリするので、
Svelteが劇的にすごい！とは思いませんでした。（今回のVueアプリでは使っていませんでしたが・・・）

もう少し大きいアプリで試してみると、メリット・デメリットがわかってくると思うので、またいずれチャレンジしてみたいです。