カテゴリー
【Svelte入門】Svelte5で値の変化を監視するObservableなパターンをRuneで実装する
※ 当ページには【広告/PR】を含む場合があります。
2024/10/24

直近の2024年10月、いよいよ
これによって、色々と実装の作法がガラっと変わります。
特に
+ letを宣言する位置による作用(トップレベルかそれ以外か)
+ 「export let」
+ 「$: ...」等のリアクティブ構文
+ <script>と<script context="module">との違い
+ $$props/$$restProps
+ ライフサイクル (onMountなど)
+ Store APIと「$」プレフィックス
ちょっとしたことですが、個人レベルのSveltekitプロジェクトで、コンポーネントの一つで旧式となったリアクティブ構文「$」を「Rune」で置き換えたときのポイントを記事として記しておきます。
Svelte4以前のリアクティブ構文を使ったObservableパターン
まず先に、今後使う機会が少なくなってくるSvelte4以前の「リアクティブ構文」の内容を総括としてダイジェストな感じでまとめてみましょう。
「$」ブロックに処理を小出しに入れていく
おそらくSvelte初学者が最初に触れるリアクティブな使いかたとして、
「$: ...」
この方法では監視対象としたい変数を宣言(と初期化)、監視対象が変化した際の処理、を複数のブロックで記述するものです。
$: hoge = '初期化!';
$: console.log(`HOGE曰く「${hoge}」`);
このパターンは、Observable的な挙動というより、javascriptの伝統的なイベントコールバックを行っているに過ぎません。
なお、処理の部分が長くなるときには、別の関数として切り出して、以下のようにします。
$: hoge = '初期化!';
$: hogeSays(hoge);
function hogeSays(_msg) {
console.log(`HOGE曰く「${_msg}」`);
}
なんだか関数として分離する書き方が格好悪く感じる場合、即時関数を利用して以下のように書けます。
$: hoge = '初期化!';
$: hoge, (() => {
console.log(`HOGE曰く「${hoge}」`);
})();
個人的にはこちらの即時関数を使う書き方が好みで多用していました。
ちなみに、このテクニックの応用として、一つの
「$:」
「,」
$: hoge = '初期化!';
$: piyo = '初期化!';
$: hoge, (() => {
console.log(`HOGE曰く「${hoge}」`);
})(), piyo, (() => {
console.log(`PIYO曰く「${piyo}」`);
})();
「writable」を使う
Svelteでより進んだリアクティブ構文による変数監視イベントを実装するために、
Store APIの中でも特に
先程のリアクティブ構文の例をwritableストアで置き換えるなら以下のようになるでしょう。
import { writable } from "svelte/store";
const hoge = writable('初期化!');
$: $hoge, hogeSays();
function hogeSays() {
console.log(`HOGE曰く「${$hoge}」`);
}
もちろん、即時関数形にもできます。
import { writable } from "svelte/store";
const hoge = writable('初期化!');
$: $hoge, (() => {
console.log(`HOGE曰く「${$hoge}」`);
})();
こうやって書くと、なんとなく理解できるとはおもいますが、
$: hoge = '初期化!';
///👇構文的に等価
const hoge = writable('初期化!');
のようにみなすことができます。
注意点として、writableストアを使って変数を宣言した場合、Writableクラスのインスタンスになるため、中身の値を参照したい場合に
「$」
ただし、writableストアを使って、わざわざリアクティブ構文と組み合わせる必要は実際のところ冗長であり、
const hoge = writable('初期化!');
hoge.subscribe(v => {
console.log(`HOGE曰く「${v}」`);
});
としておいて、あとはどこかのイベントコールバックで
$hoge = ...
なお、通常は別ファイルで
store.js
import { writable } from "svelte/store";
export const hoge = writable('初期化!');
としておいて、イベントを共有したいコンポーネントで以下のようにSubscribeさせておきます。
import { hoge } from "./store.js";
hoge.subscribe(v => {
console.log(`HOGE曰く「${v}」`);
});
Svelte5からのRuneによるObservableパターン
先程の内容からも分かる通り、「$を使ったリアクティブ構文」と「Store API」の2つの機能が密に競合してしまっており、Svelte初学者の方にとっては無用な混乱を招く問題になっていました。
そこでSvelte5からは新しいリアクティブなAPI・
このRuneの登場に伴い、
+ 「$: ...」(リアクティブ構文)
--> 廃止
+ Store API
--> 非推奨とはならないまでも、後方互換のためしばらく残される
という扱いに変わります。
Runeの基本的な概念は
古くは15年ほど遡るKnockout.jsなどのJSフレームワークに端を発し、Solid.jsによって洗練され、現在は多くのJSフレームワークに採用されてきた経緯があります。
Signalでは、
State(Observable)
Effect(Computed)
Derived(Pure-computed)
もっとも早くにこのSignalモデルを提唱したKnockout.js時代の端的な実装例は以下のようになります。
const count = ko.observable(0);
const doubleCount = ko.pureComputed(() => count() * 2);
//Logs whenever doubleCount updates
ko.computed(() => console.log(doubleCount()))
見ての通り、簡素な記述の取り決めであり、シンプルが故にコード実装者には解りやすいのがSignalの大きな利点と言えます。
かつての理科の時間に習うような、"てこの原理"や"フレミングの左手の法則"のように、3つのキーワードを押さえることでユーザーの頭に入りやすいという効果もあるのかもしれません。
RuneによるObservableパターンの実装
Svelte4から5へのマイグレーションの詳しい内容は、以下のガイドを読んでください。
まずはプロジェクトをSvelte5へアップグレードしていない場合には以下のコマンドでバージョンを引き上げておきます。
$ yarn add svelte@latest
おもむろに、そのままローカルサーバーで立ち上あげてみると、ブラウザ側で以下のエラーが起こって止まります。
Uncaught Svelte error: component_api_invalid_new
Attempted to instantiate src/App.svelte with `new App`, which is no longer valid in Svelte 5. If this component is not under your control, set the `compatibility.componentApi` compiler option to `4` to keep it working. See https://svelte-5-preview.vercel.app/docs/breaking-changes#components-are-no-longer-classes for more information
component_api_invalid_new errors.js:86
check_target legacy.js:9
App App.svelte:13
<anonymous> main.ts:4
errors.js:86:16
component_api_invalid_new errors.js:86
check_target legacy.js:9
App App.svelte:13
<匿名> main.ts:4
Svelte5以降だと、bootstrapのやり方が異なるようです。
import './app.css'
import App from './App.svelte'
const app = new App({
target: document.getElementById('app')!,
})
export default app
これがSvelte5だと以下のように修正しないといけません。
import './app.css';
import App from './App.svelte';
//👇追加
import { mount } from 'svelte';
const app = mount(App, {
target: document.getElementById('app')!
});
export default app;
Runeよる変数の監視
では本題のRuneの使い方です。
基本は組込みで使える
$state
$derived
$effect
前節で説明したサンプルコードをRuneにしてみましょう。
let hoge = $state('初期化した状態');
$effect(() => {
console.log(`HOGE曰く「${hoge}」`);
});
これだけで、あとはどこかで
hoge = ...
$effect
とにかくSvelte開発者の覚えておくことは、Signalモデルの3つの骨子、
$state
$derived
$effect
慣れていけば、他のRuneである
$props
$bindable
$inspect
$host
まとめ
以上、今回はSvelte5で初登場となった『Rune』を使う上で知っておきたい事前知識を交えながら、過去のリアクティブ構文の置き換えテクニックをちょっとだけ紹介しました。
Svelte5では、過去のバージョンのSvelteで悪かった点を軒並み清算するくらいドラスティックに修正されています。
これからSvelteを始めようとされる方には、過去のSvelteから学ぶより、Svelte5から入られたほうがよりスッキリと学習することができるでしょう。
参考サイト
記事を書いた人
ナンデモ系エンジニア
主にAngularでフロントエンド開発することが多いです。 開発環境はLinuxメインで進めているので、シェルコマンドも多用しております。 コツコツとプログラミングするのが好きな人間です。
カテゴリー