Ccmmutty logo
Commutty IT
0 pv8 min read

Svelteの学習メモ ルーン編

https://cdn.magicode.io/media/notebox/blob_9vJ1tSV
こんにちは! 今日も引き続き、Svelteの学習をしていきます。
前回、$state() なんぞこれ。みたいな話があったんですが、
前回の記事はこちら
今回はこれについて、もう少し深掘りしていきます。
こちら、ルーンと呼ばれるもので、Svelte5から、導入されたものらしいですね。 意味は、
神秘的で魔法的なシンボルとして使用される文字、またはマーク。
なんか魔法感があっていいかも…!

ルーンが導入された理由

こちらのルーンですが、Svelte5から導入されたもので、以前はなかったようです。
ルーンの導入された理由については、こちらに書いてありました。 https://svelte.jp/blog/runes
一見すると、これは後退しているように見えるかもしれません。Svelte らしくない(un-Svelte-like)、と。デフォルトで let count がリアクティブであるほうが良いのではないか?と。
いいえ、それは違います。実際、アプリケーションが複雑になってくるにつれ、どの値がリアクティブでどの値がリアクティブでないのか判別するのが難しくなってくるのです。また、このヒューリスティックはコンポーネントのトップレベルにある let 宣言でのみ機能するため、混乱を招く可能性があります。コードの振る舞いが .svelte ファイル内と .js で異なっていると、コードのリファクタリングが難しくなります。例えば、何かを store にして複数の場所で使えるようにする必要がある場合などです。
むむむ。どうやら、アプリケーションが複雑になってくると、リアクティブな値の判別が難しくなってくるようですね。 実際に比較してみましょう。
<script>
  let count = 0;
  function handleClick() {
    count += 1;
  }
 
  $: doubled = count * 2;
</script>
 
<button on:click={handleClick}>
  Clicked {count}
  {count === 1 ? "time" : "times"}
</button>
 
<p>{count} doubled is {doubled}</p>
これがSvelte5以前のコード
<script>
  let count = $state(0);
  function handleClick() {
    count += 1;
  }
 
  let doubled = $derived(count * 2);
</script>
 
<button onclick={handleClick}>
  Clicked {count}
  {count === 1 ? "time" : "times"}
</button>
<p>{count} doubled is {doubled}</p>
これがSvelte5のコード。
これぐらいのコード規模だとまだ判別しやすいけど、確かに規模が大きくなってくると、あれ? これリアクティブな変数だっけ? って見分けがつかなくなりそうです。 変数名で工夫する方法もあるとは思いますが、変えた方が都合がよかったんでしょうね。たぶん。

そもそもリアクティブな変数とは?

リアクティブ変数は、その変数を変更するだけで、画面に変更が反映される変数です。 前回と同様に、ボタンを押したときに、カウントアップするプログラムを例にあげます。
シンプルにこんなやつ。
リアクティブ変数がない状態だと、こんな感じ
<script>
	let count = 0;
	function incCounter()
	{
		count++;
		document.getElementById("counter").innerHTML = "count:" + count;
		
	}
</script>

	<button id="counter" onClick="incCounter()">count:0</button>
リアクティブ変数がある場合、こんな感じ
<script>
	let count = $state(0);

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

<button onclick={incCounter}>
	count:{count}
</button>
比べてみると、結構シンプルに書ける印象ですね!
このコードの例だと、まだ恩恵は少ないかもしれないですが、 要素が増えてくると、内容が書き換わっているかなどのチェック項目が増えるので、 確かに便利そうです! リアルタイムに値が書き換わっている感じも、なんか見てて楽しい

ルーンの種類

そんなルーンの種類ですが、大きく分けて7つ存在するらしいです。

$state

今まで何度も出てきているやつですね。 $stateで変数を宣言することで、リアクティブ変数であることを示すそうな。
<script>
	let count = $state(0);

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

<button onclick={incCounter}>
	count:{count}
</button>
このコードだと、countをリアクティブ変数として宣言してるおかげで、画面に値が即座に変更されるみたいですね。

$derived

しれっと上の例で出してたのですが、$derivedは、他のリアクティブ変数からの派生を宣言するもののようです。
<script>
  let count = $state(0);
  function handleClick() {
    count += 1;
  }
 
  let doubled = $derived(count * 2);
</script>
 
<button onclick={handleClick}>
  Clicked {count}
  {count === 1 ? "time" : "times"}
</button>
<p>Doubled : {doubled}</p>
ボタンを押して、countの変数の値が変わったときに、連動して、doubledの値も変わってくれるようです。 便利!

$effect

$effectを使うと、この関数内で使用している、state変数や、derivedを監視して、変更があったら、実行してくれるものっぽいです。
<script>
	let count = $state(0);

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

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

<button onclick={handleClick}>
	Clicked {count}
	{count === 1 ? 'time' : 'times'}
</button>
このコード例だったら、常にcount変数を見てくれてて、10以上になったら、アラート出してくれるみたいですね。 この関数内で検知してくれるのありがたいかも。

$props

$propsは、コンポーネントを使う時に、使用するようです。 (コンポーネントの説明については、今回割愛。なんかパーツに分けられて便利なやつです。)
このようなコードがあった場合
App.svelte
<script>
	import Component from "./Component.svelte";
</script>

<Component />
<Component name="world" />
Component.svelte
<script>
	let {name = 'men'} = $props();
</script>



# Hello {name}
nameの内容を変える事で、色んなパーツができそうです。 なんかよくわからないけど、便利そう! 複数ページにある、タイトル名だけをページ毎に変えるとか、そういうので使えるのかな。
ドキュメントみてると、より色んな使い方ができそうだけど、今回は一旦ここで終わっとく。

$bindable

基本的には、親コンポーネントから、子コンポーネントに値を渡す流れのようなのですが、$bindableを使うことで、子コンポーネントから、親コンポーネントに値を渡すことができるようです。 …とはいえ多用すると、流れに沿っていないようなので、必要な場所でだけ使うのがいいみたい。
以下、Inputフィールドで入力したものを、名前に反映させるサンプルです。
App.svelte
<script>
	import Input from './Input.svelte';
	let name = $state('world');
</script>

<Input bind:text={name}/>


# Hello {name}!
bind:textで値を割り当てているみたい
Input.svelte
<script>
	let { text=$bindable() } = $props();
</script>

<input type="text" bind:value={text} />
こちらのフィールドでは、bind使ってますね。
すると、inputフィールドに入力したものがリアルタイムに反映されてる!

$inspect

$inspectは、リアクティブ変数の値をチェックし、変更があったら、console.logを実行してくれるものみたいです。 こちらは、開発中のみに機能して、本番用のビルドでは何も実行されない様子。
Input.svelte
<script>
	import Input from './Input.svelte';
	let name = $state('world');

	$inspect(name);
</script>
<Input bind:text={name}/>


# Hello {name}!
先ほどのInput.svelteに、早速追加してみたところ。 こんな感じに表示されるようです。

$host

$hostは、コンポーネントをカスタム要素としてコンパイルする時に使用し、host要素にアクセスできるもののようです。
以下、サンプルです。
Stepper.svelte
<svelte:options customElement="my-stepper" />

<script>
	function dispatch(type) {
		$host().dispatchEvent(new CustomEvent(type));
	}	
</script>

<button onclick={() => dispatch('decrement')}>decrement</button>
<button onclick={() => dispatch('increment')}>increment</button>
App.svelte
<script>
	import './Stepper.svelte';
	
	let count = $state(0);
</script>

<my-stepper
	ondecrement={() => count -= 1}
	onincrement={() => count += 1}
></my-stepper>

<p>count: {count}</p>
見てると、自分でイベントハンドラとか、要素名をつけられるっぽい?
まだまだルーンも深掘りすれば、色んな使い方ができそうですが、今回はこの辺で。

Discussion

コメントにはログインが必要です。