サイトトップ

Director Flash 書籍 業務内容 プロフィール

HTML5テクニカルノート

RxJS 6入門 01: RxJSを使ってみる


RxJSはリアクティブプログラミングのJavaScriptライブラリです。リアクティブプログラミングとは、データを時間軸にもとづくオブジェクトの流れとして表し、値やイベントを受け取ったとき関連したプログラムが反応(react)して処理するというプログラミングの考え方です。非同期の処理やイベントを扱うコードがわかりやすく組み立てられます。本稿では、RxJSライブラリをインストールしたうえで、公式サイトの「Overview」に沿っていくつか短いコードを書いて試してみます。

01 RxJSライブラリをCDNで読み込む

RxJSをすぐに試すには、CDNから読み込むのが手軽でしょう。HTMLドキュメントの<head>要素に、<script>要素をつぎのように加えます。本稿執筆時の最新リリースバージョンは6.2.0です。

<head>要素

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

<script>要素に読み込んだRxJSライブラリのグローバルな名前空間はrxjsです。用いるクラスや関数は、オブジェクトの分割代入の構文(ECMAScript 2015)で、たとえばつぎのようにあらかじめ定数に定めておくとよいでしょう。


const {Observable, fromEvent} = rxjs;

npmを用いたインストールの仕方についてはGitHubの「Installation and Usage」をご覧ください。なお本稿執筆時では、RxJSサイトの「Installation」の情報はまだ古いまま改訂されていないようです。トップページにはベータサイトである旨(WARNING: This is BETA site)が記されていますので、注意した方がよいでしょう。

02 RxJSとは

RxJSは非同期処理やイベントにもとづくプログラムを、複数のobservableオブジェクトの流れとして組み立てるライブラリです。Observableクラスを軸に、ObserverSchedulersSubjectsといったクラスやオペレータ(関数)が提供されます。Arrayクラスの新しいメソッド(map()filter()reduce()every()など)を参考にした構文で、非同期のイベントが整理して処理できるのです。ライブラリのもととなっているReactiveXは、ObserverIteratorのパターンを組み合わせ、さらに関数型プログラミングも用いて、つづけていくつも起こるイベントを扱います。

RxJSが非同期のイベントを管理する仕組みは、おもにつぎの要素で成り立ちます。

03 イベントを捉える

簡単なイベントの扱いからはじめましょう。<body>要素につぎのように<buttun>要素を加えておきます。

<body>要素

<button type="button">button</button>

まずは、標準のJavaScriptコードでつぎのようにイベントリスナーを使います。なお、用いる構文はECMAScript 2015 (ECMAScript 6)です。ボタンをクリックすれば、ブラウザのコンソールにテキスト(clicked!)が示されます。

標準JavaScript

const button = document.querySelector('button');
button.addEventListener('click', (event) => console.log('clicked!'));

RxJSでは、Observableインスタンスをつくるのが基本です。fromEvent()関数は、引数にターゲットとイベントを渡すと、Observableオブジェクトが返されます。イベントが起こったときのコールバックを定めるのがObservable.subscribe()メソッドです。つぎのコードは、やはりボタンクリックで、コンソールにテキスト(clicked!)を出力します。

RxJS

const {fromEvent} = rxjs;
const button = document.querySelector('button');
fromEvent(button, 'click')
.subscribe((event) => console.log('clicked!'));

04 関数型プログラミングの手法を用いる

RxJSは関数型プログラミングの手法を採り入れています。エラーが少なくなり、たとえ起こったとしても見つけやすいコードが書けるのです。前項のコードに手を加えて、クリック回数が示されるようにしましょう。すると、標準のJavaScriptコードでは、つぎのように回数を納める変数(count)がなければなりません。そしてこの変数は、別のコードから書き替えることもできてしまいます[*1]。ボタンをクリックするたびに回数は1ずつ加算されますので、動きは問題ありません。

標準JavaScript

let count = 0;
const button = document.querySelector('button');
button.addEventListener('click', (event) => console.log(`count: ${++count}`));

関数型プログラミングでは、処理を純粋な関数で加えてゆきます(「純粋関数について」参照)。Observable.scan()は、第1引数に渡したコールバックの戻り値をつぎの引数に渡します。第2引数(0)ははじめのコールバックが受け取る初期値です。Array.reduce()メソッドと考え方は同じです。つぎのRxJSのコードで、ボタンクリックの回数(count)がカウントアップされます。そして、回数は引数で渡されるので、外からは参照できません。

RxJS

const {fromEvent} = rxjs;
const {scan} = rxjs.operators;
const button = document.querySelector('button');
fromEvent(button, 'click')
.pipe(
	scan((count) => ++count, 0)
)
.subscribe((count) => console.log(`count: ${count}`));

[*1] 標準のJavaScriptコードでも、回数の値を外からは隠せます。つぎのように、イベントリスナーを即時関数で定め、回数は引数で与えればよいのです。ただし、わかりやすいコードとはいえなくなります。

標準JavaScript

button.addEventListener('click',((count) => 
	(event) => console.log(`count: ${++count}`)
)(0));

05 イベントの流れを操作する

RxJSのオペーレータ(関数)は、イベントの流れをObservableによりさまざまに操作できます。前項のコードで、ボタンクリックの処理はつづけざまに受けつけず、一定の時間(1秒間)は無視することにしましょう。標準のJavaScriptコードでは、つぎのように前回処理した時間を変数(lastClick)で覚えておかなければなりません。補助的な変数(rate)も増えます。ブラウザのコンソールにはクリックした回数のほか間隔秒数も示されますので、処理時間が1秒以上開いているのを確かめられるでしょう。

標準JavaScript

let count = 0;
const rate = 1000;
let lastClick = Date.now() - rate;
const button = document.querySelector('button');
button.addEventListener('click', (event) => {
	if (Date.now() - lastClick >= rate) {
		console.log(`count: ${++count}, sec: ${Math.floor(Date.now() / 100) % 1000 / 10}`);
		lastClick = Date.now();
	}
});

RxJSでは、throttleTime()オペレータが引数のミリ秒数の間、値(イベント)の出力を止めます。このメソッドを、つぎのように処理がはじまる前に差し込んでしまえばよいのです。時間はオペレータの引数に与えるだけですから、別に変数にとっておかなくて済みます。

RxJS

const {fromEvent} = rxjs;
const {throttleTime, scan} = rxjs.operators;
const button = document.querySelector('button');
fromEvent(button, 'click')
.pipe(
	throttleTime(1000),
	scan((count) => ++count, 0)
)
.subscribe((count) => console.log(
	`count: ${count}, sec: ${Math.floor(Date.now() / 100) % 1000 / 10}`
));

値の流れを制御するオペーレータには、ほかにもたとえばつぎのようなものがあります。

06 処理に値を加える

前項のコードに、さらに処理する別の値を加えます。調べるのは、ボタンクリック(clickイベント)のとき[shift]キーを押していたかどうかです。イベントのコールバックが受け取る引数から、MouseEvent.shiftKeyプロパティでブール(論理)値が得られます。押していたら、カウント数(count)を減らすことにしましょう。

標準JavaScript

let count = 0;
const rate = 1000;
let lastClick = Date.now() - rate;
const button = document.querySelector('button');
button.addEventListener('click', (event) => {
	if (Date.now() - lastClick >= rate) {
		count += event.shiftKey ? -1 : 1;
		console.log(`count: ${count}, sec: ${Math.floor(Date.now() / 100) % 1000 / 10}`);
		lastClick = Date.now();
	}
});

RxJSのmap()オペレータは、引数の関数が返す値をObservableで出力します。以下のように、戻り値がつぎのオペレータscan()のコールバックに第2引数(shiftKey)として渡るのです。コードの実際の動きは、jsdo.itに掲げた以下のサンプル001でお確かめください。

RxJS

const {fromEvent} = rxjs;
const {throttleTime, map, scan} = rxjs.operators;
const button = document.querySelector('button');
fromEvent(button, 'click')
.pipe(
	throttleTime(1000),
	map(event => event.shiftKey),
	scan((count, shiftKey) => count + (shiftKey ? -1 : 1), 0)
)
.subscribe((count) => console.log(`count: ${count}, sec: ${Math.floor(Date.now() / 100) % 1000 / 10}`));

サンプル001■RxJS 6 + ES6: Using RxJS

値をつくって加えるオペーレータには、ほかにもたとえばつぎのようなものがあります。

RxJS 6入門


作成者: 野中文雄
作成日: 2018年5月28日


Copyright © 2001-2018 Fumio Nonaka.  All rights reserved.