Open12

Svelte勉強

kyoh86kyoh86

Svelte。読み方は /'svεlt/ 。ビルド時に色々やることで実行時コストを下げてるのが売りみたい。
component、rune、bindあたりがキーになってきそう。

.svelteというファイルにコンポーネントを作り、コンポーネントはHTML, CSS, JavaScriptで構成される。…と聞くとちょっとAngularっぽいな。
JavaScriptの代わりに直接TypeScriptを書くのもサポートされてる。最高だぜェ

kyoh86kyoh86

スクリプトは普通に <script> ... </script> で書ける。tsなら <script lang="ts">...</script>
module level logicなるものも一応書けるみたいだけど滅多に書かなくてイイって。
<script module> ... </script>
スタイルも普通に <style>...</style>

kyoh86kyoh86

DOMの中からscriptの値が参照できて、

<script>
    let name = "hoge"
</script>
<p>{hoge}</p>

みたいな書き方もできる。属性にも色々渡せる。

<p class="{name}-cl">hoge</p> // 属性にも埋め込める
<p {name}>hoge</p> // name={name} の省略
kyoh86kyoh86

特殊な変数を宣言する方法として、 Runes というのがある。
状態を持つ $state とか、従属する状態を宣言する $derived とか。

let count = $state(0);
let double = $derived(count * 2);

derivedの使い方とか、ビルド時にちゃんとやってんな感が強くてイイ。
こういうオブジェクトはProxyを通されていて、Proxyの上で値をいじっても元のオブジェクトには影響しないそう。

<script>
	let raw = {value: 0};
	let count = $state(raw);
	let double = $derived({value: count.value * 2});

	function increment() {
		count.value++;
		console.dir(raw);
	}
</script>

<p>{count.value} * 2 = {double.value}</p>
<p>raw: {raw.value}</p>
<button onclick={increment}>count up</button>

count増やしてもrawは増えない。まあそれはそう。

kyoh86kyoh86

状態状態言ってるけど、「リアクティブ値」と呼ばれるものらしい。Proxyされてる理解は正しい。

kyoh86kyoh86

ProxyはDeep Reactivityを実現してる。再帰的に内部の変更を追っかけてくれるんだ。キモい技術だ。

kyoh86kyoh86

console.log()とかで直接リアクティブ値を出力すると警告出るので、中身をちゃんと見るのがイイ

console.log($state.snapshot(count))

だし、リアクティブ値の変化に応じて自動でログを出す $inspect なるものもある

$inspect(count)
kyoh86kyoh86

$effect がかなりムズい。けど…要は「Svelteでは預かり知らない領域に影響(effect)を及ぼすための処理」を宣言するためのモノっぽい。
<canvas> に何か描いたりとかもそれ。
実行タイミング的には「DOMの更新が行われた後」に実行されるよう。

$effect(() => {
...
});
kyoh86kyoh86

$effectは、依存するリアクティブ値が更新された時に再実行されるらしい。
依存を明示する必要は特にない。リアクティブ値の読み取りを自動で依存として導き出してくれる。

let state1 = $state(...)
$effect(() => {
...
   state1;  // この状態の更新に応じて(DOMの更新後に)再実行される。
...
});

ただ、このリアクティブ値の読み取りはsetTimeoutのコールバックとか、 await の後みたいな、非同期処理の中で読み取っても依存とみなされないよう。
これは結構要注意だな。うっかりawait前段に挟むと挙動変わったりするわけだ。

kyoh86kyoh86

なおかつ、リアクティブ値の変更に基づく再実行は「再帰的に値の追跡をしない」とのこと。

An effect only reruns when the object it reads changes, not when a property inside it changes. (If you want to observe changes inside an object at dev time, you can use $inspect.)

DOMに単純に埋め込んだ場合に反映される基準(あっちは再帰的に見てくれる)とは違うのね。要注意。
$derivedは再アサインが起きるから反映されるってのがまたややこしい。


<script>
	let state = $state({ value: 0 });
	let derived = $derived({ value: state.value * 2 });

	// this will run once, because `state` is never reassigned (only mutated)
	$effect(() => {
		state;
	});

	// this will run whenever `state.value` changes...
	$effect(() => {
		state.value;
	});

	// ...and so will this, because `derived` is a new object each time
	$effect(() => {
		derived;
	});
</script>

<button onclick={() => (state.value += 1)}>
	{state.value}
</button>

<p>{state.value} doubled is {derived.value}</p>
kyoh86kyoh86

そして$effectの中のcallbackで関数を返すと、cleanup functionとして機能してくれる。
effectが再実行される前に必ず実行される。
例えば

$effect(() => {
    const id = setInterval(() => {
        console.log("hoge")
    }, 1000)
    ...
    return () => {
        clearInterval(id); // 次のsetIntervalが走る前に消しておく
    }
})
kyoh86kyoh86

コンポーネントの入れ子は

<script>
    import Nested from './Nested.svelte';
</script>
<Nested />

こう。