Svelte チュートリアル学習ノート その1

## 動機
現在、日本語学習者向けの漢字学習アプリを開発しています。
バックエンドに Bun & Elysia を採用することは決定しているので、
フロントエンドはできれば React, Vue, Svelte あたりから選びたいと考えています。
本記事は Svelte を触って何かやってみるための準備の記録です。

## 教材
[Svelte チュートリアル](https://learn.svelte.jp/)
本記事は **Part 1: Basic Svelte** の前半部分 (Introduction, Reactivity, Props) のノートです。

## Svelteを使うと何がうれしいのか
* コンポーネントベースでWebアプリが構築できる
* JavaScriptにコンパイルされるため、**オーバーヘッドがない**
* 1つのコンポーネント (`.svelte` ファイル) にHTML, CSS, JavaScript を記述でき、自己完結させやすい

## 基本的な書き方

### {} を使ってJSコードを埋め込む
JSX/TSXと同様です。
```html:App.svelte
<script>
	let src = '/image.gif';
	let name = 'Rick Astley';
</script>

<img src={src} alt="{name} dances." />
<!-- <img src="/image.gif" alt="Rick Astley dances." /> -->
```
なお、`src={src}` のように属性名と変数名が同じ場合、シンプルに `{src}` とも書けます。

---
### `import` 文でコンポーネントを読み込む
```html:App.svelte
<script>
	import AnotherComponent from './AnotherComponent.svelte';
</script>
```

---
### `{@html ...}` で文字列をHTMLとしてレンダリングする
```html:App.svelte
<script>
	let string = `this string contains some <strong>HTML!!!</strong>`;
</script>

<p>{@html string}</p>
```

this string contains some **HTML!!!**

## Reactivity システム (DOM を更新する)
### イベントハンドラを定義する
```html:App.svelte
<script>
	let count = 0;

	function increment() {
		count++;
	}
</script>

<button on:click={increment}>
	Clicked {count}
	{count === 1 ? 'time' : 'times'}
</button>
```
`on:click={...}` 部分がイベントハンドラにあたります。
このケースでは、ボタンをクリックしたときに `increment()` が発火し、count が更新され再レンダリングされます。

---
### `$:` を使って、関連するDOMを更新させる (Reactive declaration: リアクティブ宣言)
```html:App.svelte
<script>
	let count = 0;

	$: doubled = count * 2;

	function increment() {
		count += 1;
	}
</script>

<button on:click={increment}>
	Clicked {count}
	{count === 1 ? 'time' : 'times'}
</button>

<p>{count} doubled is {doubled}.</p>
```
`$: doubled = count * 2;` という宣言をすることで、`count` が更新された時に、 `doubled` の再代入と再レンダリングが行われます。

「 `doubled` の再代入」と書きましたが、より正しくは、 `$:` 以降の処理の再実行 (*re-action*) が行われるので、値の設定だけでなく、関数の実行もできます。制御構文も書けます。
```js
$: console.log(`Someone seems to have pressed the button.`);
```
```js
$: if (age === 18) {
	alert('Congratulations! You are now ready to vote.');
}
```

リアクティビティは代入によってトリガーされますが、`push()`, `pop()` などのいわゆる**破壊的メソッドによってはトリガーされません**。
愚直ですが、自身を再代入することでトリガーできます。
```js
function addNumber() {
	numbers.push(numbers.length + 1);
	numbers = numbers;  // この文がないと、push だけでは numbers に対するリアクションが発火しない
}
```

## `export` で子コンポーネントへのデータ受け渡しを行う (いわゆる props)
```html:Child.svelte
<script>
	export let place;
</script>

<p>Stairway to {place}</p>
```
```html:App.svelte
<script>
	import Nested from './Nested.svelte';
	let place = "Heaven"
</script>

<Nested place={place} />
```
**結果**
```
Stairway to Heaven
```

### props のデフォルト値を設定する
値を代入しておけばよいです。
```html:Child.svelte
<script>
	export let place = 'Paradise';
</script>

<p>Stairway to {place}</p>
```
```html:App.svelte
<script>
	import Nested from './Nested.svelte';
	let place = "Heaven"
</script>

<Nested />
```
**結果**
```
Stairway to Paradise
```

### props の受け渡しにはスプレッド構文が使える
さもありなん、というところです。
```html:NovelInfo.svelte
<script>
	export let title;
	export let author;
	export let nationality;
	export let publishedYear;
</script>

<p>
	"{title}" is a novel by {nationality} author {author}, first published in {publishedYear}.
</p>

```
```html:App.svelte
<script>
	import NovelInfo from './NovelInfo.svelte';

	const book = {
		title: 'Confessions of a Mask',
		author: 'Yukio Mishima',
		nationality: 'Japanese',
		publishedYear: '1949'
	};
</script>

<PackageInfo {...book} />
```
**結果**
```
"Confessions of a Mask" is a novel by Japanese author Yukio Mishima, first published in 1949.
```

## 雑感
`useState` や `useEffect` などの hooks ががっちりと用意されている React.js と比べて、学習・実装コストとも低いと感じました。
この調子でチュートリアルを進めていきたいです。