Zademy

Vanilla JavaScriptで学ぶRxJS基礎コース

RxJS; JavaScript; ReactiveProgramming
words 単語

1. リアクティブプログラミングとは

マウスのクリック、テキスト入力、HTTPレスポンス。これらは全部「時間とともに流れてくるデータ」です。リアクティブプログラミングは、そのデータの流れ(ストリーム)を扱う考え方です。

「データ来たかな?」と何度も聞きに行くのではなく、届いた瞬間に反応する。これだけの発想の転換なんですが、非同期処理の見え方が劇的に変わります。

2. RxJSとは

RxJSは、Observableを中心に非同期処理やイベント処理を組み立てるJavaScriptライブラリです。ざっくり言えばPromiseの強化版で、複数回値を流せるし、途中でキャンセルもできる。

インストール

npm:

npm install rxjs

CDN:

<script src="https://unpkg.com/rxjs@7/dist/bundles/rxjs.umd.min.js"></script>

Vanilla JSの基本構成

index.html、app.js、あとはお好みでstyles.css。この3ファイルだけで始められます。フレームワークは不要です。

3. Observable

Observableは、時間の経過とともに0件、1件、複数件の値を発行するデータソースです。私は「値を流す蛇口」みたいに理解しています。ひねると値が出て、閉めると止まる。

const { of, from, interval } = rxjs;

of(1, 2, 3).subscribe(console.log); // 1, 2, 3
from(["A", "B"]).subscribe(console.log); // A, B

fromEvent

DOMイベントもObservableに変換できます。個人的に、RxJSの便利さが一番実感できる機能だと思っています。

const { fromEvent } = rxjs;
const button = document.getElementById("btn");

const click$ = fromEvent(button, "click");
const sub = click$.subscribe(() => console.log("click"));

subscribe / unsubscribe

購読を始めると値が流れてきますが、使い終わったら忘れずに解除してください。intervalやfromEventのような無限ストリームは放置するとメモリリークの原因になります。

const { interval } = rxjs;

const sub = interval(1000).subscribe(v => console.log("tick", v));
setTimeout(() => sub.unsubscribe(), 5000);

4. 基本オペレーター

オペレーターはObservableの値を変換したりフィルタしたりする関数です。pipeで繋げて使います。この「繋ぐ」感覚がRxJSの核心で、UNIXのパイプと似た感覚で扱えます。

const { of } = rxjs;
const { map, filter, take } = rxjs.operators;

of(1, 2, 3, 4, 5)
	.pipe(
		filter(n => n % 2 === 1),
		map(n => n * 10),
		take(2)
	)
	.subscribe(console.log); // 10, 30

このコースでよく使うオペレーターを挙げておきます。生成系はof、from、interval、timer。変換系はmap、filter。結合系はmerge、concat。抽出系はtake、first、debounceTime。

5. DOMイベントの処理

クリックカウンター

一番シンプルな例です。クリックをカウントして画面に表示します。

const { fromEvent } = rxjs;

const btn = document.getElementById("btn");
const output = document.getElementById("output");
let count = 0;

fromEvent(btn, "click").subscribe(() => {
	count += 1;
	output.textContent = `Clicks: ${count}`;
});

ライブ検索(基本)

入力欄の値を監視して、300ms入力が止まったら「検索」とみなす。このdebounceTimeのパターンは実務でも本当によく使います。

const { fromEvent } = rxjs;
const { map, debounceTime } = rxjs.operators;

const input = document.getElementById("search");

fromEvent(input, "input")
	.pipe(
		map(e => e.target.value.trim()),
		debounceTime(300)
	)
	.subscribe(term => console.log("Search:", term));

6. 非同期処理

Observable と Promise の違い

Promiseは通常1回だけ値を返します。一方Observableは、複数回値を流せてunsubscribeで中断できる。この差が、イベント処理やポーリングでは決定的に効いてきます。

RxJSのajaxでAPI呼び出し

const { ajax } = rxjs.ajax;

ajax
	.getJSON("https://jsonplaceholder.typicode.com/users")
	.subscribe({
		next: data => console.log("Users:", data),
		error: err => console.error("HTTP error:", err),
		complete: () => console.log("Completed")
	});

7. エラーハンドリングと完了処理

const { of } = rxjs;
const { catchError, retry } = rxjs.operators;
const { ajax } = rxjs.ajax;

ajax
	.getJSON("https://api.example.com/data")
	.pipe(
		retry(2),
		catchError(err => {
			console.error("Request failed:", err);
			return of([]);
		})
	)
	.subscribe({
		next: data => console.log(data),
		complete: () => console.log("Finished")
	});

覚えておきたいのは4つだけです。catchErrorはエラー時の代替処理、retryは失敗時の再試行、completeは正常終了時の処理、unsubscribeは手動での停止。これだけ押さえておけば、実務の大部分はカバーできます。

8. ベストプラクティス

実際に使っていて気をつけていることです。

無限ストリーム(interval、fromEvent)は必ず購読解除する。pipeでオペレーターを連結して、読みやすさを保つ。重い処理をsubscribeのコールバックに詰め込まない。デバッグにはtapを挟むと、値の流れが追いやすくなります。

9. 最終プロジェクト(提案)

ここまでの内容を1つのアプリにまとめてみましょう。ライブ検索ミニアプリを作ります。

テキスト入力を作り、fromEventで入力イベントを監視、mapとdebounceTimeで整形、ajax.getJSONでAPIを呼んで、結果を画面に表示。最後にcatchErrorでエラー処理を入れる。これだけでも基礎内容が1本の実践フローで繋がります。

10. 参考リンクと次のステップ

公式ドキュメントで読んでおきたいページ:

練習課題をいくつか。intervalでストップウォッチ、+/-ボタンのカウンター、公開APIを使ったライブ検索、retryによる再試行処理。どれも30分もあれば作れます。

次は中級編でswitchMap、mergeMap、Subject、ストリーム合成に進むと、実務で使える幅がぐっと広がります。