Svelteのすすめ

この記事は社内勉強会用に作成したものです。

(2024/12/27 追記)
現時点最新バージョンのSvelte5についての記事を公開しました。よろしけばこちらもご覧ください。

https://qiita.com/kurata04/items/4c511e17fed7982aea1b

# Svelte

- [公式サイト][official]

## 目次

- [Svelte とは？](#svelte-とは)
- [特徴](#特徴)
- [記述方法](#記述方法)

## Svelte とは？

- JavaScript フレームワーク
- フレームワークだがコンパイラーであるという特異性も備えている
  - 実行前にコードを最適化したJavaScriptにコンパイルする = 軽い、速い
  - ReactやVueは実行時にコードを解釈する = 重い、遅い
- 開発者へのアンケート結果 
  - [最も愛されている Web フレームワーク(2021)](https://insights.stackoverflow.com/survey/2021#section-most-loved-dreaded-and-wanted-web-frameworks) 
  - [開発者が最も満足しているフレームワーク(2020)](https://2020.stateofjs.com/en-US/technologies/front-end-frameworks/) 
  -  [最も学習したいフレームワーク(2022)](https://tsh.io/state-of-frontend/#which-of-the-following-frameworks-would-you-like-to-learn-in-the-future)

## 特徴

- Write less code
- No virtual DOM
- Truly reactive

### Write less code

少ないコード量で書ける

> 全てのコードにはバグが存在する可能性があります(All code is buggy)。したがって、書かなければいけないコードが多ければ多いほど、アプリケーションがバグだらけになるのは理にかなっています。

> 重要なのは読みやすさ(Readability is important)
> 　巧妙なトリックを使って読みやすさを犠牲にしてでもできるだけコードをコンパクトにするべきだ、と主張しているわけではありません。

```js
for (let i = 0; i <= 100; i += 1) {
  if (i % 2 === 0) {
    console.log(`${i} is even`)
  }
}
```

↓ 読みやすさを犠牲にコード量を減らす

```js
for (let i = 0; i <= 100; i += 1) if (i % 2 === 0) console.log(`${i} is even`)
```

#### 他のフレームワークとの比較

入力された 2 つの数字の足し算

- React: 442 文字

```react
import React, { useState } from "react"

export default () => {
  const [a, setA] = useState(1)
  const [b, setB] = useState(2)

  function handleChangeA(event) {
    setA(+event.target.value)
  }

  function handleChangeB(event) {
    setB(+event.target.value)
  }

  return (
    <div>
      <input type="number" value={a} onChange={handleChangeA} />
      <input type="number" value={b} onChange={handleChangeB} />

      <p>
        {a} + {b} = {a + b}
      </p>
    </div>
  )
}
```

- Vue: 263 文字

```vue
<template>
  <div>
    <input type="number" v-model.number="a" />
    <input type="number" v-model.number="b" />

    <p>{{ a }} + {{ b }} = {{ a + b }}</p>
  </div>
</template>

<script>
export default {
  data: function () {
    return {
      a: 1,
      b: 2,
    }
  },
}
</script>
```

- Svelte: 156 文字

```vue
  <script>
    let a = 1;
    let b = 2;
  </script>

  <div>
    <input type="number" bind:value={a}>
    <input type="number" bind:value={b}>

    <p>{a} + {b} = {a + b}</p>
  </div>
```


### No virtual DOM

仮想 DOM を使わない

> '仮想 DOM は速い'という神話を完全に終わりにしよう

- DOM

  - HTML や XML 文書の構造を表現するためのインタフェース
  - 開発者ツールで見られる

- 仮想 DOM

  - ブラウザ上の実際の DOM と同じような構造を持つ JavaScript オブジェクトのツリー
  - 仮想的な DOM の変更内容をもとに実際の DOM を変更する(最小限の更新が可能)
  - React や Vue などで使用されている

#### Svelte の 差分更新

- コンパイル時に変更される可能性がある箇所を最適化された JavaScript コードに変換する
- 変更時には仮想 DOM を使わずに DOM の必要箇所のみを変更できる
  → 差分検出のコストが必要ない
  → 軽くて速い

### Truly reactive

真のリアクティブ(特別なコードを書くことなく UI が更新される)

- React

  ```react
    const [count, setCount] = useState(0);

    function increment() {
        setCount(count + 1);
    }
  ```

- Vue

  ```js
    let count = ref(0);

    function increment() {
      count.value += 1;
    }
  ```

- Svelte

  ```js
  let count = 0;

  function increment() {
      count += 1;
  }
  ```



## Svelteの始め方

### ローカル

```bash
  npm create svelte@latest myapp
  cd myapp
  npm install
  npm run dev
```

### オンラインコードエディタ

[svelte REPL](https://svelte.jp/repl/hello-world?version=3.59.2)

## 記述方法

[チュートリアル](https://svelte.jp/tutorial/basics)が充実している。

### Hello world

```html
<script>
  let name = 'world';
</script>

<h1>Hello {name}!</h1>
```

### 省略記法

```html
  <img src={src} alt="A man dances.">
```

↓

```html
  <img {src} alt="A man dances.">
```

### リアクティビティ

- 値を変更するだけで画面に反映される

```js
  function incrementCount() {
    count += 1;
  }
```

#### リアクティブ宣言

- 変数と変数を同期させる

  ```js
    // countが変更されるとdoubledも変更される
    let count = 0;
    $: doubled = count * 2;
  ```

- ステートメントを同期させる

  ```js
    $: console.log('the count is ' + count);
  ```

- ブロック

  ```js
    $: {
      console.log('the count is ' + count);
      alert('I SAID THE COUNT IS ' + count);
    }
  ```

- if 文

  ```js
    $: if (count >= 10) {
      alert('count is dangerously high!');
      count = 9;
    }
  ```

#### 配列やオブジェクトの場合

> 大まかなまとめ: 更新される変数は代入の左側に直接置かなければならない。

- 自身を代入する

  ```js
    function addNumber() {
      numbers.push(numbers.length + 1);
      numbers = numbers;
    }
  ```

- スプレッド構文を使う

  ```js
    function addNumber() {
      numbers = [...numbers, numbers.length + 1];
    }
  ```

### HTML内のロジック

分岐や繰り返しなどのロジックは`{}`を用いる

> \# の文字は常に ブロックの開始 タグを示します。
> / の文字は常に ブロックの終了 タグを示します。
> : の文字は {:else} のように ブロックの継続 タグを示します。

#### if 文

```vue
  {#if x > 10}
    <p>{x} is greater than 10</p>
  {:else if 5 > x}
    <p>{x} is less than 5</p>
  {:else}
    <p>{x} is between 5 and 10</p>
  {/if}
```

#### each 文

第二引数として index を取得できる

```vue
  <ul>
    {#each cats as cat, i}
      <li><a target="_blank" href="https://www.youtube.com/watch?v={cat.id}" rel="noreferrer">
        {i + 1}: {cat.name}
      </a></li>
    {/each}
  </ul>
```

#### await

非同期処理によって処理の終了を待つ

```vue
  {#await promise}
    <p>...waiting</p>
  {:then number}
    <p>The number is {number}</p>
  {:catch error}
    <p style="color: red">{error.message}</p>
  {/await}
```

[official]: https://svelte.jp/