Svelte勉強
Svelte。読み方は /'svεlt/
。ビルド時に色々やることで実行時コストを下げてるのが売りみたい。
component、rune、bindあたりがキーになってきそう。
.svelteというファイルにコンポーネントを作り、コンポーネントはHTML, CSS, JavaScriptで構成される。…と聞くとちょっとAngularっぽいな。
JavaScriptの代わりに直接TypeScriptを書くのもサポートされてる。最高だぜェ
スクリプトは普通に <script> ... </script>
で書ける。tsなら <script lang="ts">...</script>
module level logicなるものも一応書けるみたいだけど滅多に書かなくてイイって。
<script module> ... </script>
スタイルも普通に <style>...</style>
DOMの中からscriptの値が参照できて、
<script>
let name = "hoge"
</script>
<p>{hoge}</p>
みたいな書き方もできる。属性にも色々渡せる。
<p class="{name}-cl">hoge</p> // 属性にも埋め込める
<p {name}>hoge</p> // name={name} の省略
特殊な変数を宣言する方法として、 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は増えない。まあそれはそう。
状態状態言ってるけど、「リアクティブ値」と呼ばれるものらしい。Proxyされてる理解は正しい。
$effect
がかなりムズい。けど…要は「Svelteでは預かり知らない領域に影響(effect)を及ぼすための処理」を宣言するためのモノっぽい。
<canvas>
に何か描いたりとかもそれ。
実行タイミング的には「DOMの更新が行われた後」に実行されるよう。
$effect(() => {
...
});
$effectは、依存するリアクティブ値が更新された時に再実行されるらしい。
依存を明示する必要は特にない。リアクティブ値の読み取りを自動で依存として導き出してくれる。
let state1 = $state(...)
$effect(() => {
...
state1; // この状態の更新に応じて(DOMの更新後に)再実行される。
...
});
ただ、このリアクティブ値の読み取りはsetTimeoutのコールバックとか、 await
の後みたいな、非同期処理の中で読み取っても依存とみなされないよう。
これは結構要注意だな。うっかりawait前段に挟むと挙動変わったりするわけだ。
なおかつ、リアクティブ値の変更に基づく再実行は「再帰的に値の追跡をしない」とのこと。
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>
そして$effectの中のcallbackで関数を返すと、cleanup functionとして機能してくれる。
effectが再実行される前に必ず実行される。
例えば
$effect(() => {
const id = setInterval(() => {
console.log("hoge")
}, 1000)
...
return () => {
clearInterval(id); // 次のsetIntervalが走る前に消しておく
}
})
コンポーネントの入れ子は
<script>
import Nested from './Nested.svelte';
</script>
<Nested />
こう。