すぐに使えるSvelteハンドブック

# はじめに
今後爆発的人気になるかもしれないし、ならないかもしれない[**Svelte**](https://svelte.jp/)の基本的な記述のやり方についてまとめてみました。
フロント開発に関わっている方や、フロント開発を勉強中の方に読んでいただけたらと思います。
※**React**や**Vue**での開発経験があったほうが、よりわかりやすい内容となっています。

:::note warn
注意
この記事は**Svelteの基本的な記述のみ**をまとめているため、Svelteの概要や、アニメーションの使い方などは割愛していますのでご了承ください。
:::

# 「やりたいことから」リンク集
**■基本情報**
[プロジェクト作成](#プロジェクト作成)
[HTML、CSS、JSの記述](#htmlcssjsの記述)

**■データ**
[reactive値を扱う](#reactive値を扱う)
[再計算、再実行させる](#再計算再実行させる)

**■コンポーネント**
[コンポーネントの作成、呼び出し(通常、slot)](#コンポーネントの作成呼び出し通常slot)
[propsを扱う](#propsを扱う)

**■イベント**
[DOMイベントと関数を紐づける](#domイベントと関数を紐づける)
[コンポーネントからイベントを発信させる](#コンポーネントからイベントを発信させる)

**■ロジック**
[表示を分岐させる(表示、非表示)](#表示を分岐させる表示非表示)
[表示を繰り返す](#表示を繰り返す)
[非同期処理を扱う](#非同期処理を扱う)

**■バインド**
[テキスト入力欄を使う](#テキスト入力欄を使う)
[チェックボックスを使う](#チェックボックスを使う)
[ラジオボタンを使う](#ラジオボタンを使う)
[セレクトボックスを使う](#セレクトボックスを使う)
[子コンポーネントの入力要素にバインドさせる](#子コンポーネントの入力要素にバインドさせる)

**■ライフサイクル**
[mount時に処理を実行させる](#mount時に処理を実行させる)
[unMount時に処理を実行させる](#unmount時に処理を実行させる)
[DOM更新前に処理を実行させる](#dom更新前に処理を実行させる)
[DOM更新後に処理を実行させる](#dom更新後に処理を実行させる)

**■Store**
[Storeを作成する](#storeを作成する)
[Storeの値を取得する](#storeの値を取得する)
[Storeの値を変更する](#storeの値を変更する)

# プロジェクト作成
`test-project`の部分を任意のプロジェクト名に変えてください。
※この記事は`TypeScript`での記述を前提に書いています。

```
$ npx degit sveltejs/template test-project
$ cd test-project
$ node scripts/setupTypeScript.js
$ npm install
$ npm run dev
```

### VSCodeのパッケージを追加
`VSCode`を使用する方は、任意で入れてみてください。

https://marketplace.visualstudio.com/items?itemName=svelte.svelte-vscode

# HTML、CSS、JSの記述
**svelte**は、`JS`、`HTML`、`CSS`の順番で記述する。

- **script**内に宣言した変数は`{}`を使用することでHTML内に反映できる。
- **CSS**は`scoped`となっている。

```vue:App.svelte
<script lang="ts">
  let text = 'Hello World'
</script>

<h1 class="title">{text}</h1>

<style>
  .title {
    color: red;
  }
</style>
```

**属性名**と**変数名**が同じ場合は省略できる。


```vue:App.svelte
<script lang="ts">
  let src = './○○.png'
</script>

<img src={src} alt="image"> <!-- ↓と同じ -->
<img {src} alt="image">
```

# reactive値を扱う
**svelte**では`let`で変数宣言をすると、自動的に`reactive`な値となる。

```vue:App.svelte
<script lang="ts">
  let count = 0
</script>

<h1>{count}</h1>
```

:::note warn
注意
**svelte**のリアクティビティは代入によってトリガーされるため、**push**や**splice**のような配列メソッドを使っても自動的には更新されない。
:::


# 再計算、再実行させる
変数や関数の前に`$:`を記述することで、再計算、再実行の対象となる。

```vue
<script lang="ts">
  let count = 0

  // countの値が変更されると再計算される
  $: doubled = count * 2

  // 任意の処理を再実行することもできる
  $: console.log('the count is ' + count);

  // if文の前にも記述できる
  $: if (count >= 10) {
    alert('count is dangerously high!');
    count = 9;
  }
</script>
```

# コンポーネントの作成、呼び出し(通常、slot)

## 通常のコンポーネントの場合

```vue:App.svelte
<script lang="ts">
  import Nested from './Nested.svelte';
</script>

<Nested />
```

```vue:Nested.svelte
<script lang="ts">
  let text = 'Hello World'
</script>

<p>{text}</p>
```

## slotの場合

```vue:App.svelte
<script lang="ts">
  import Box from './Box.svelte';
</script>

<Box>
  <p>Hello World</p>
</Box>
```

```vue:Box.svelte
<div>
  <slot></slot>
</div>
```

# propsを扱う
**props**として渡される値の前に`export`を記述することで、「**propsとして渡される値**」と認識される。

```vue:App.svelte
<script lang="ts">
  import Nested from './Nested.svelte';
</script>

<Nested answer={42} />
```

```vue:Nested.svelte
<script lang="ts">
  export let answer;
  export let name = 'Taro'; // あらかじめ値を代入しておくとデフォルト値にできる
</script>

<p>name: {name}</p>
<p>The answer is {answer}</p>
```

プロパティを持っているオブジェクトを渡す場合、`...`で簡略化できる

```vue:App.svelte
<script lang="ts">
  import Nested from './Nested.svelte';

  const user = {
    name: 'Taro',
    age: 20
  }
</script>

<Nested {...user} />
```

```vue:Nested.svelte
<script lang="ts">
  export let name;
  export let age;
</script>

<p>名前: {name}</p>
<p>年齢: {age}</p>
```

# DOMイベントと関数を紐づける
イベント名の前に`on:`を記述する。

```vue:App.svelte
<script lang="ts">
  let count = 0;

  const countUp = () => {
	count += 1
  }
</script>

<h2>{count}</h2>
<button on:click={countUp}>
 Count Up
</button>

```

子、孫コンポーネントのDOMイベントの場合`イベントフォワーディング`をする。

```vue:App.svelte
<script lang="ts">
  import CustomButton from './CustomButton.svelte';

  const handleClick = () => {
	alert('Button Clicked');
  }
</script>

<CustomButton on:click={handleClick}/>
```

```vue:CustomButton.svelte
<button on:click>
  Click me
</button>
```

`|`を使用し、イベントに**修飾子**をつけられる。複数指定可能。

```vue
<script lang="ts">
  const handleClick = () => {
	alert('alerts')
  }
</script>

<!-- once の場合、1回のみ実行される -->
<button on:click|once={handleClick}>
  Click me
</button>

<!-- 複数指定する場合 -->
<button on:click|once|self={handleClick}>
  Click me
</button>
```

**イベント修飾子の一覧**

- `preventDefault`・・・実行する前に`event.preventDefault()`を呼び出す。
- `stopPropagation`・・・`event.stopPropagation()`を呼び出す。
- `passive`・・・`タッチ/ホイール`イベントでスクロールのパフォーマンスを向上させる。(svelteが自動的に追加)
- `nonpassive`・・・`passive: false`を明示的に設定。
- `capture`・・・**バブリング**フェーズではなく、**キャプチャ**フェーズ中に実行する。
- `once`・・・ハンドラを最初に実行した後に削除する。
- `self`・・・設定した要素が`event.target`の場合にのみ、実行する。
- `trusted`・・・`event.isTrusted`が`true`の場合にのみ、実行する。

# コンポーネントからイベントを発信させる
Vueの`emit`みたいなもの。
`createEventDispatcher`を使用し、イベントを発信。

```vue:App.svelte
<script lang="ts">
  import Child from './Child.svelte';

  const handleMessage = (event) => {
	alert(event.detail.text);
  }
</script>

<Child on:message={handleMessage}/>
```

```vue:Child.svelte
<script lang="ts">
  import { createEventDispatcher } from 'svelte';

  const dispatch = createEventDispatcher();

  const sayHello = () => {
	dispatch('message', {
	  text: 'Hello!'
	});
  }
</script>

<button on:click={sayHello}>
	Click to say hello
</button>
```

中間コンポーネントがある場合は`フォワーディング`する必要がある。

```vue:App.svelte
<script lang="ts">
  import Child from './Child.svelte';

  const handleMessage = (event) => {
	alert(event.detail.text);
  }
</script>

<Child on:message={handleMessage}/>
```

```vue:Child.svelte
<script lang="ts">
  import Grandchild from './Grandchild.svelte';
</script>

<Grandchild on:message/>
```

```vue:Grandchild.svelte
<script lang="ts">
  import { createEventDispatcher } from 'svelte';

  const dispatch = createEventDispatcher();

  const sayHello = () => {
	dispatch('message', {
	  text: 'Hello!'
	});
  }
</script>

<button on:click={sayHello}>
  Click to say hello
</button>
```

# 表示を分岐させる(表示、非表示)
`#` の文字は常にブロックの**開始**タグを示す。 
`:` の文字は `{:else}` のようにブロックの**継続**タグを示す。
`/` の文字は常にブロックの**終了**タグを示す。 

```vue:App.svelte
<script lang="ts">
  let isLogin = false;

  const toggle = () => {
	isLogin = !isLogin;
  }
</script>

{#if isLogin}
  <button on:click={toggle}>
    Log out
  </button>
{:else}
  <button on:click={toggle}>
	Log in
  </button>
{/if}
```

`else if`ももちろん使える

```vue:App.svelte
<script lang="ts">
  let x = 7;
</script>

{#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}
```

# 表示を繰り返す
**svelte**で表示の繰り返しをする場合は`each`を使用する。

```vue:App.svelte
<script lang="ts">
  let users = [
	{ name: 'Taro', age: 20 },
	{ name: 'Hana', age: 23 },
	{ name: 'Ken', age: 25 }
  ];
</script>

<ul>
  {#each users as user}
	<li>
      <p>name: {user.name}</p>
      <p>age: {user.age}</p>
    </li>
  {/each}
</ul>
```

第２引数でindexを指定できる。

```vue
{#each users as user, i}
  <li>
    <p>number: {i}</p>
    <p>name: {user.name}</p>
    <p>age: {user.age}</p>
  </li>
{/each}
```

一意な識別子(key)

```vue
{#each users as user (user.name)}
{/each}
```

# 非同期処理を扱う

```vue:App.svelte
<script lang="ts">
  const fetchData = async() => {
	const res = await fetch('https://○○○');

	if (res) {
	  return 'OK';
	} else {
	  throw new Error('Error');
	}
  }
	
  let result = fetchData();
  
  const promiseTest = () => {
    result = fetchData();
  }
</script>

<button on:click={promiseTest}>
  fetch
</button>

{#await result}
  <!-- 処理中の表示を記述 -->
{:then string}
  <!-- resolved時の表示を記述 -->
{:catch error}
  <!-- rejected時の表示を記述 -->
{/await}
```

`reject`できないことがわかっている場合は、catch ブロックを省略することが可能。

```vue
{#await result}
  <p>...waiting</p>
{:then string}
  <p>The text is {string}</p>
{/await}
```
`resolve`するまで何も表示したくない場合は、最初のブロックを省略することもできる。

```vue
{#await result then value}
  <p>The text is {value}</p>
{/await}
```
# テキスト入力欄を使う
`bind:value`を使用することで、`reactive値`と`input値`を紐づけることができる。
vueの`v-model`と同じ。
※bindの名前と変数名が一致する場合は省略できる。
**textarea**タグでも同様。

```vue:App.svelte
<script lang="ts">
  let value = '世界';
</script>

<input bind:value={value}> <!-- ↓と同じ -->
<input bind:value>

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

`type="number"`や`type="range"`を使用したときは、数値として解釈してくれる。

```vue:App.svelte
<script lang="ts">
  let num = 2;
</script>

<input type=number bind:value={num}>

<h1>{num * num}</h1>
```

# チェックボックスを使う
チェックボックスの場合は、`bind:checked`を使用する。

```vue:App.svelte
<script lang="ts">
 let isChecked = false;
</script>

<label>
  <input type=checkbox bind:checked={isChecked}>
  check box
</label>

<button disabled={!isChecked}>
  Subscribe
</button>
```

# ラジオボタンを使う
ラジオボタンのようにグループ化が必要な場合は、`bind:group`を使用する。

```vue:App.svelte
<script lang="ts">
  let radioValue = 1
</script>

<label>
  <input type=radio bind:group={radioValue} name="radioValue" value={1}>
  No.1
</label>

<label>
  <input type=radio bind:group={radioValue} name="radioValue" value={2}>
  No.2
</label>

<label>
  <input type=radio bind:group={radioValue} name="radioValue" value={3}>
  No.3
</label>
```

# セレクトボックスを使う
セレクトボックスも`bind:value`を使用する。

```vue:App.svelte
<script lang="ts">
  let questions = [
	{ id: 1, text: 'question1' },
	{ id: 2, text: 'question2' },
	{ id: 3, text: 'question3' }
  ];

  let selected;
</script>

<select bind:value={selected}>
  {#each questions as question}
	<option value={question}>
	  {question.text}
	</option>
  {/each}
</select>
```




# 子コンポーネントの入力要素にバインドさせる
DOM要素のプロパティにバインドできるのと同様に、コンポーネントの`props`にもバインドできる。

```vue:App.svelte
<script lang="ts">
  import Child from "./Child.svelte";

  let name = '';
</script>

<Child bind:name={name} />
<h1>{name}</h1>
```

```vue:Child.svelte
<script lang="ts">
  export let name: string
</script>

<input type="text" bind:value={name}>
```

# mount時に処理を実行させる
`onMount`を使用することで、コンポーネントが最初に`DOM`にレンダリングされた後に処理を実行できる。

```vue:App.svelte
<script lang="ts">
  import { onMount } from 'svelte';

  onMount(() => {
	console.log('Mounted!!')
  });
</script>

<h1>onMount</h1>
```

# unMount時に処理を実行させる

`onDestroy`を使用することで、コンポーネントが破棄されるときに処理を実行できる。

```vue:App.svelte
<script lang="ts">
  import { onDestroy } from 'svelte';

  onDestroy(() => {
	console.log('Destroy!!')
  });
</script>

<h1>onDestroy</h1>
```

# DOM更新前に処理を実行させる

`beforeUpdate`を使用することで、コンポーネントが最初にマウントされる前と、`DOM`が更新される直前に処理を実行できる。

```vue:App.svelte
<script lang="ts">
  import { beforeUpdate } from 'svelte';

  beforeUpdate(() => {
	console.log('Update!!')
  });
</script>

<h1>beforeUpdate</h1>
```
# DOM更新後に処理を実行させる
`afterUpdate`を使用することで、`DOM`がデータと同期した後に処理を実行できる。

```vue:App.svelte
<script lang="ts">
  import { afterUpdate } from 'svelte';

  afterUpdate(() => {
	console.log('Updated!!')
  });
</script>

<h1>afterUpdate</h1>
```
# Storeを作成する
外部から読み書き可能な`writable`と、外部からは読み取り専用の`readable`がある。
`readable`は第1引数に**初期値**、第2引数に**値を変更するための関数**が必要。

```ts:stores.ts
import { writable, readable } from 'svelte/store';

export const count = writable(0); // 読み書き可能

// 読み取り専用
export const time = readable(new Date(), function start(set) {
  const interval = setInterval(() => {
	set(new Date());
  }, 1000);

  return function stop() {
	clearInterval(interval);
  };
});
```

# Storeの値を取得する
Storeから読み取る値には`$`の記述が必要。

```vue:App.svelte
<script lang="ts">
  import { count } from './stores';
</script>

<h1>The count is {$count}</h1>
```

```ts:stores.ts
import { writable } from 'svelte/store';

export const count = writable(0);
```

# Storeの値を変更する
現在のStoreの値を利用する`update`と、直接変更する`set`がある。

```vue:App.svelte
<script lang="ts">
  import { count } from './stores';
  import Counter from './Counter.svelte'
</script>

<h1>The count is {$count}</h1>
<Counter />
```


```vue:Counter.svelte
<script lang="ts">
  import { count } from './stores';

  const increment = () => {
	count.update(n => n + 1); // storeの現在の値を引数に取り更新
  }

  const reset = () => {
	count.set(0); // storeの値を直接変更
  }
</script>

<button on:click={increment}>increment</button>
<button on:click={reset}>reset</button>
```

```ts:stores.ts
import { writable } from 'svelte/store';

export const count = writable(0);
```

# おわりに
ここまでお読みいただきありがとうございました。
以上が**Svelte**の基本的な記述のやり方です。
**Svelte**はとてもシンプルで使いやすいので、これからどんどん需要が高まっていくことを期待しています！
記述ミスやわかりにくい点などございましたら、お手数ですが、コメントや編集リクエストなどで送っていただけたら幸いです。
これからも少しずつ**Svelte**をキャッチアップしていきたいと思います。