はいさい!オースティンやいびーん!
概要
SvelteとRxJSを一緒に使う方法を紹介します。
驚くほど簡単です。
背景
Svelteは最近、欧米のWeb界隈で急速に名を上げて求心力を高めつつあります。その主な理由は、パフォーマンスの良さです。
そして、そのパフォーマンスは本当に関心の種です。
SvelteはJavaScriptのフレームワークなのですが、独自のSyntaxを提唱し、JavaScriptの方言、派生言語といった方がいいくらい不思議な構文をします。
それをコンパイル時に驚くほど簡単な、高効率のJavaScriptに変換されます。
しかも、軽いんです。
バンドルサイズもWeb ComponentsのLitと同じくらいになります。
恐ろしいフレームワークです。
そしてRxJS。
RxJSは非同期処理、イベント配信系の処理の問題を強力なツールで解決してくれるライブラリーです。
解決してくれるというより、解決できるが使い方はお任せするよ、というスタンスで、難易度が高いと言われています。
RxJSは、MicrosoftとGoogleを中心にオープンソースで開発されていますが、JavaScriptにReactive Programmingを戻らす元祖ライブラリーです。
Vue、React、Angular、SolidJSなど、あらゆる現代的フロントエンドフレームワークは大きくRxJSから影響を受けているし、多くは内部で使っています。
徐々に一般の開発者も手に触れる機会が増えているのですが、以前として 難しすぎる ので敬遠されがちです。
ただ、一度Functional ProgrammingおよびRxJSに深く浸かって身を染めてしまうと、二度と戻れなくなるほど、有効で痛快なツールです。
読者はまだ試したことなければ、是非是非お勧めします。
簡単にいってしまえば、RxJSは簡単な非同期処理をやや難しくするが、複雑な処理を簡単にする、というような特徴があります。
技量を一気に上げたければRxJSを自分の武器にすることです。
SvelteとRxJSの出会い
そこで、パフォーマンスのいいSvelteと、感動するほど力持ちのRxJSの出会いは、奇跡的な偶然です。
Svelteにはステート管理ツールのStoreを購読するためのロジックが、偶然的に全く、全く RxJSのObservableと同一なのです。
そのおかげでSvelteにRxJSを簡単に入れて幸せな結婚に導くことができます。
とんでもない奇跡で、筆者はワクワクが止まらない。
ではお見せしましょう!
簡単にObservableをSvelteのテンプレートで購読する
SvelteのStoreをテンプレートで購読する場合、{$myStore}
のように、$
を追加します。
Observableに対して同じことをやってみましょう。
<script lang="ts">
import { interval } from 'rxjs';
let int$ = interval(1000); // Observable<number>
</script>
<h1>Welcome to Svelte.±</h1>
<h2>My int: {$int$}</h2>
これをブラウザで見てみると、
見事なまでに、そのまま使えちゃう。
というか、不安になるくらい、使えちゃう。
メモリリークは起きていない?無限ループは起きていない?
と不安になったあなた、大丈夫です。
SvelteのStoreがたまたま同じSyntaxでStoreに対して.subscribe
を読んで、あたいが来るまでundefined
として定義しています。
Svelteの部品が画面から消えて消滅する時に、Storeの購読を.unsubscribe
で止めるようになっています。
これはObservableと全く同じなのです。
それで、魔法みたいに相性よく使えます。
なぜこうなったのか?本当に偶然らしいです。ただ、冒頭でいったように、RxJSは上級者向けではあるのですが、Reactive Programmingの元祖でもあるので、フレームワークを開発するものならば、必ずその存在を知っているはずです。
そこで、SvelteのStoreもおそらくRxJSを意識して作ったものでしょう。そのおかげでより強力なRxJSをStoreとして使えるわけです。
ユーザー入力をSubjectに流す方法
もう一歩踏み込んで筆者は、Subjectは使えないだろうか? という疑問が湧きました。
結論、これも可能です。
ただ、一つだけ課題があります。
それは、Storeの場合、.set
を読んでステートを更新しているのに対してRxJSのSubjectらは.next
のメソッドです。
どうしたらいいでしょうか?諦めるか?やっぱり無理か?
開発者の端くれなら以下のような解決策を思いつくはずです。
import { Subject } from 'rxjs';
export class SvelteSubject<T> extends Subject<T> {
set(value: T) {
this.next(value);
}
}
なんと、新しいメソッドを追加するだけで完全な互換性が手に入るのです!
以下の例を見てみましょう。ユーザーがTodoの番号を入れれば、そのTodoを取得して表示する部品です。
<script lang="ts">
import { filter, map, mergeMap, sampleTime, switchMap } from 'rxjs';
import { SvelteSubject } from './svelte-subject';
let input$ = new SvelteSubject<string>();
let result$ = input$.pipe(
sampleTime(500), // 500msの感覚でしか値を取らない、無駄にリクエストを送るのを止める
map((input) => Number(input)),
filter((number) => !isNaN(number) && number > 0),
switchMap((num) => fetch(`https://jsonplaceholder.typicode.com/todos/${num}`)), // switchMapで前のリクエストがまだ終わっていなければキャンセルして新しいリクエストを送る
filter((response) => response.ok),
mergeMap((result) => result.json())
);
</script>
<h1>Todo Search</h1>
<label for="todo-search">Enter Todo Number:</label>
<input id="todo-search" type="number" bind:value={$input$} />
{#if $result$}
<article>
<h1>{$result$.title}</h1>
{#if $result$.completed}
<p class="completed">Completed!</p>
{/if}
</article>
{/if}
<style>
.completed {
color: red;
}
</style>
これも見事にうまくいくんです。
もちろん同様なことをRxJSなしでSvelteのツールでできるでしょう。ただ、筆者はそれを知らなくともRxJSで見事に完結なコードでやや複雑な実装をたった数分でできたわけだし、メモリリークも無駄なリクエストもなしにできたわけなのです。
これがRxJSの力で、Svelteをよく知らずともすぐにいいものが作れる気がしてきました。
ちなみに、この実装をSvelteKitでビルドした結果、やく
15kbでした。
まとめ
いかがでしょうか?筆者と同様に驚きましたでしょうか?
一気にSvelteの可能性が広がったようで、今後僕が何かのプロジェクトで採用する可能性も同様に一気に高まりました
Svelteのテンプレートの構文もとてもまともで使いやすいので嬉しかったです。
AngularでしかRxJSをフルに活用できないと考えていた筆者ですが、Svelteでもできるとなれば、パフォーマンスが重要なアプリケーションならSvelteを採用すると言えるようになりました。
Angularはコードの整理とオブジェクト指向がフルにできるという観点で、Svelteに大きく優っていますが、Zone.jsのせいでパフォーマンスが悪いんです
Apple Watchのような端末だと、パンクしてしまうのが目に見えています。
皆さんも、RxJSとSvelteの組み合わせをどうぞお試しあれ!