【Svelte入門】Svelte5で値の変化を監視するObservableなパターンをRuneで実装する


※ 当ページには【広告/PR】を含む場合があります。
2024/10/24
蛸壺の技術ブログ|Svelte5で値の変化を監視するObservableなパターンをRuneで実装する

直近の2024年10月、いよいよ
『Svelte5』が正式にリリースがアナウンスされました。

Release svelte@5.0.0

これによって、色々と実装の作法がガラっと変わります。

特に
『Rune』と位置づけられたリアクティブの新しい構文の登場によって、v4以前とv5以降で過去のプロジェクトをマイグレする場合に大なり小なり開発者に負担が生じることが予想されます。

            
            + letを宣言する位置による作用(トップレベルかそれ以外か)
+ 「export let」
+ 「$: ...」等のリアクティブ構文
+ <script><script context="module">との違い
+ $$props/$$restProps
+ ライフサイクル (onMountなど)
+ Store APIと「$」プレフィックス
        
ちょっとしたことですが、個人レベルのSveltekitプロジェクトで、コンポーネントの一つで旧式となったリアクティブ構文「$」を「Rune」で置き換えたときのポイントを記事として記しておきます。


合同会社タコスキングダム|蛸壺の技術ブログJavascript(js)&Typescript(ts)プログラミング入門〜これから学ぶ人のためのおすすめ書籍&教材の手引き

超JavaScript 完全ガイド 2024

Svelte4以前のリアクティブ構文を使ったObservableパターン

「Observable」パターンは文字通り、変数の監視を行って、値が変化した際に、Subscribeとして登録した処理を行わせる手法です。

まず先に、今後使う機会が少なくなってくる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」を利用する必要があります。

Store APIの中でも特に
「writableストア」の使い方が基本となります。

先程のリアクティブ構文の例を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}」`);
});
        

合同会社タコスキングダム|蛸壺の技術ブログJavascript(js)&Typescript(ts)プログラミング入門〜これから学ぶ人のためのおすすめ書籍&教材の手引き

超JavaScript 完全ガイド 2024

Svelte5からのRuneによるObservableパターン

先程の内容からも分かる通り、「$を使ったリアクティブ構文」と「Store API」の2つの機能が密に競合してしまっており、Svelte初学者の方にとっては無用な混乱を招く問題になっていました。

そこでSvelte5からは新しいリアクティブなAPI・
『Rune』に機能集約を行うことで、より理解しやすく、ユーザーの使いやすい方向に持っていくように大幅な修正が行われてきた経緯があります。

このRuneの登場に伴い、

            
            + 「$: ...」(リアクティブ構文)
    --> 廃止

+ Store API
    --> 非推奨とはならないまでも、後方互換のためしばらく残される
        
という扱いに変わります。

Runeの基本的な概念は
『Signal』という考え方に基づいています。

参考|The Evolution of Signals in JavaScript

古くは15年ほど遡るKnockout.jsなどのJSフレームワークに端を発し、Solid.jsによって洗練され、現在は多くのJSフレームワークに採用されてきた経緯があります。

Signalでは、
State(Observable)Effect(Computed)Derived(Pure-computed)の重要な3つのコンセプトが存在します。

もっとも早くにこの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つのキーワードを押さえることでユーザーの頭に入りやすいという効果もあるのかもしれません。


合同会社タコスキングダム|蛸壺の技術ブログJavascript(js)&Typescript(ts)プログラミング入門〜これから学ぶ人のためのおすすめ書籍&教材の手引き

超JavaScript 完全ガイド 2024

RuneによるObservableパターンの実装

Svelte4から5へのマイグレーションの詳しい内容は、以下のガイドを読んでください。

参考|Svelte 5 migration guide

まずはプロジェクトを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も必要に応じて使っていけば良いでしょう。


合同会社タコスキングダム|蛸壺の技術ブログJavascript(js)&Typescript(ts)プログラミング入門〜これから学ぶ人のためのおすすめ書籍&教材の手引き

超JavaScript 完全ガイド 2024

まとめ

以上、今回はSvelte5で初登場となった『Rune』を使う上で知っておきたい事前知識を交えながら、過去のリアクティブ構文の置き換えテクニックをちょっとだけ紹介しました。

Svelte5では、過去のバージョンのSvelteで悪かった点を軒並み清算するくらいドラスティックに修正されています。

これからSvelteを始めようとされる方には、過去のSvelteから学ぶより、Svelte5から入られたほうがよりスッキリと学習することができるでしょう。

参考サイト

Svelte 5 migration guide

記事を書いた人

記事の担当:taconocat

ナンデモ系エンジニア

主にAngularでフロントエンド開発することが多いです。 開発環境はLinuxメインで進めているので、シェルコマンドも多用しております。 コツコツとプログラミングするのが好きな人間です。

合同会社タコスキングダム|蛸壺の技術ブログJavascript(js)&Typescript(ts)プログラミング入門〜これから学ぶ人のためのおすすめ書籍&教材の手引き