Svelte 5 に導入予定の新機能 Rune の紹介

# はじめに

2023年9月20日の夜、YouTube で以下の動画を見つけました。

https://www.youtube.com/watch?v=RVnxF3j3N8U

Svelte 5 の新機能の紹介ということで、とても興奮しました！

Svelte 5 はまだリリース予定は決まってませんが、上記の動画のように導入予定の新機能が紹介されていたので簡単に紹介したいと思います。

# ブログ

こちらのブログに上記の動画と同じことが書かれています。

https://svelte.dev/blog/runes

しかも、既に日本語訳もされてます！

https://svelte.jp/blog/runes

tomoam さんという方が翻訳してくれたみたいです。ありがとうございます！

https://twitter.com/tomoam_mat/status/1704807506130956602


# Rune

読み方は "ルーン" です。

なんと既に Svelte 5 用の REPL サイトが構築されてます！

https://svelte-5-preview.vercel.app/

というわけで、[こちら](https://svelte-5-preview.vercel.app/docs/fine-grained-reactivity) を参考に、どのような違いがあるのかを実際に動くものをお見せしたいと思います。

尚、サンプルアプリはシンプルな TODO アプリで、未完了の件数を `remaining` 関数を使ってリアクティブに表示しています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2773745/07830e14-b726-deb7-b26a-c508685f0131.png)


まずは [Rune を使わないバージョン](https://svelte.dev/repl/d16f8f2fdd3542cb88671fafb09682bd?version=4.2.1)。

```html:App.svelte
<script>
	let todos = [];

	function remaining(todos) {
		console.log('recalculating');
		return todos.filter(todo => !todo.done).length;
	}

	function addTodo(event) {
		if (event.key !== 'Enter') return;

		let done = false;
		let text = event.target.value;

		todos = [...todos, {
			done,
			text
		}];

		event.target.value = '';
	}
</script>

<input on:keydown={addTodo} />

{#each todos as todo}
	<div>
		<input bind:value={todo.text} />
		<input type="checkbox" bind:checked={todo.done} />
	</div>
{/each}

<p>{remaining(todos)} remaining</p>
```

追加した TODO アイテムのテキストボックスを編集するとコンソールログに `recalculating` が追加されます。つまり件数に影響のない変更をしているのにもかかわらず、再レンダリングが発生しちゃってます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2773745/3ed09f83-db73-38eb-fd7f-d67990d16fea.png)

Rune を使うことで改善できます。

[Rune を使うバージョン](https://svelte-5-preview.vercel.app/#H4sIAAAAAAAACmVTTY-kIBD9Kwy7iZp08O6oyR72H-yt7QMDpU2GBiNl70yM_30QsJ2PG0W9eu9VUSy0Vxocrc4LNfwGtKJ_xpGeKL6PW-DuoBF87Ow8ie2mdmJSI7ad6VADErTSOtKQ3w45Qn6-FM8-5ZP9bAQqa8gEN66MMkMesAVZtnSHwhpnNTBthzybQHAtZs3RA7PA4SET4DyZqMG8U4QpkJCmJU_bgUlroGAazIDXULR-U-dS_vPAHO5g8KGtehJv2Cu8k6emIdlf49mzgkTN1ETscRM5Wuy5drA7DCOANzzSkRb5NACyO9czFA-yfVhnxlg4n3ZDHQ5JKPcmk4kovJ52iNshkdbjkrMQfwIOydVnruDyK1eAPLhSG4kr4tbLw_vPvjw4y_ah1-WxGKZWZpyRWFP58Ur73zRLeoeVlAGx_AIurml7uAuHoFlLdW-jYmJ5UUZWQbBZwptvRhPPgdoWtumouIJ4fbFvHY11IQaZKrdx7ZV1GZWWcnMS1qYe2-X7tq7H_tbl2PqvcLNS9QokrXCaYb2sH9bw9GZFAwAA)。

```diff_html:App.svelte
<script>
-	let todos = [];
+	let todos = $state([]);

	function remaining(todos) {
		console.log('recalculating');
		return todos.filter(todo => !todo.done).length;
	}

	function addTodo(event) {
		if (event.key !== 'Enter') return;

-		let done = false;
-		let text = event.target.value;
+		let done = $state(false);
+		let text = $state(event.target.value);

		todos = [...todos, {
-			done,
-			text
+			get done() { return done },
+			set done(value) { done = value },
+			get text() { return text },
+			set text(value) { text = value }
		}];

		event.target.value = '';
	}
</script>

<input on:keydown={addTodo} />

{#each todos as todo}
	<div>
		<input bind:value={todo.text} />
		<input type="checkbox" bind:checked={todo.done} />
	</div>
{/each}

<p>{remaining(todos)} remaining</p>
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2773745/361d6121-3283-831e-efea-e76745df4c59.png)


# 様々な Rune

`$state` だけではなく、以下の様に様々な Rune があります。詳しくは[ブログ](#ブログ)や動画をご確認ください。

- $state
- $derived
- $effect
- $props

なんだか [React] っぽいですよね :laughing:

# 動機（の一部）

Rune を実装する動機としては以下もあるとのことです。

前記ブログより一部を抜粋します。

:::note
実際、アプリケーションが複雑になってくるにつれ、どの値がリアクティブでどの値がリアクティブでないのか判別するのが難しくなってくるのです。また、このヒューリスティックはコンポーネントのトップレベルにある let 宣言でのみ機能するため、混乱を招く可能性があります。コードの振る舞いが .svelte ファイル内と .js で異なっていると、コードのリファクタリングが難しくなります。例えば、何かを store にして複数の場所で使えるようにする必要がある場合などです。
:::

私は個人で小規模な開発しかしてなかったのでピンとこなかったですが、なるほどな～と思いました。


# まとめ

Svelte 5 で導入予定の Rune を簡単に紹介しました。詳しくは[ブログ](#ブログ)等をご確認ください。

今まで同様シンプルな実装方法は維持しつつ、状況に応じて Rune を使ってよりきめ細かにリアクティブな実装ができるようになっています。また、Rune は `.svelte` ファイルだけではなく `.js` ファイルでも利用できるので応用の幅が広がりそうですね。

X（旧Twitter）では様々な[反響](https://twitter.com/search?q=Svelte5&src=typed_query)があって面白いです。

私としては今までの書き方が好きだったので、それが維持される（であろう）ことが分かってちょっとホットしました。:blush:

[React]: https://react.dev/