HTML5テクニカルノート
RxJS 6入門 01: RxJSを使ってみる
- ID: FN1805013
- Technique: HTML5 / JavaScript
- Library: RxJS 6.3.3
RxJSはリアクティブプログラミングのJavaScriptライブラリです。リアクティブプログラミングとは、データを時間軸にもとづくオブジェクトの流れとして表し、値やイベントを受け取ったとき関連したプログラムが反応(react)して処理するというプログラミングの考え方です。非同期の処理やイベントを扱うコードがわかりやすく組み立てられます。本稿では、RxJSライブラリをインストールしたうえで、公式サイトの「Overview」に沿っていくつか短いコードを書いて試してみます。
01 RxJSライブラリをCDNで読み込む
RxJSをすぐに試すには、CDNから読み込むのが手軽でしょう。HTMLドキュメントの<head>
要素に、<script>
要素をつぎのように加えます。本稿執筆時の最新リリースバージョンは6.3.3です。
<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」の情報はまだ完全には改訂されておらず、CDNがリンク切れになっています。
02 RxJSとは
RxJSは非同期処理やイベントにもとづくプログラムを、複数のobservable
オブジェクトの流れとして組み立てるライブラリです。Observable
クラスを軸に、Observer
、Scheduler
、Subject
といったクラスやオペレータ(関数)が提供されます。Array
クラスの新しいメソッド(map()
、filter()
、reduce()
、every()
など)を参考にした構文で、非同期のイベントが整理して処理できるのです。ライブラリのもととなっているReactiveXは、ObserverとIteratorのパターンを組み合わせ、さらに関数型プログラミングも用いて、つづけていくつも起こるイベントを扱います。
RxJSが非同期のイベントを管理する仕組みは、おもにつぎの要素で成り立ちます。
Observable
: あとから取り出される値やイベントをまとめたオブジェクトObserver
:Observable
から送られた値を捉えるコールバックがまとまったオブジェクトSubscription
:Observable
の実行を示し、その取り消しに用いられる- オペレータ: 関数型プログラミングのやり方でデータのまとまりを処理する関数
map()
、filter()
、concat()
、flatMap()
などがある
Subject
:EventEmitter
と同じで、値やイベントを複数のObserver
にマルチキャストできるScheduler
: 複数の平行処理を制御して、いつ実行すべきかを管理するsetTimeout()
やrequestAnimationFrame()
などと同じ
03 イベントを捉える
簡単なイベントの扱いからはじめましょう。<body>
要素につぎのように<buttun>
要素を加えておきます。
<body>要素<button type="button">button</button>
まずは、標準のJavaScriptコードでつぎのようにイベントリスナーを使います。なお、用いる構文はECMAScript 2015(ECMAScript 6)です。ボタンをクリックすれば、ブラウザのコンソールにテキスト(clicked!)が示されます。
標準JavaScriptconst button = document.querySelector('button'); button.addEventListener('click', (event) => console.log('clicked!'));
RxJSでは、Observable
インスタンスをつくるのが基本です。fromEvent()
関数は、引数にターゲットとイベントを渡すと、Observable
オブジェクトが返されます。イベントが起こったときのコールバックを定めるのがObservable.subscribe()
メソッドです。つぎのコードは、やはりボタンクリックで、コンソールにテキスト(clicked!)を出力します。
RxJSconst {fromEvent} = rxjs; const button = document.querySelector('button'); fromEvent(button, 'click') .subscribe((event) => console.log('clicked!'));
04 関数型プログラミングの手法を用いる
RxJSは関数型プログラミングの手法を採り入れています。エラーが少なくなり、たとえ起こったとしても見つけやすいコードが書けるのです。前項のコードに手を加えて、クリック回数が示されるようにしましょう。すると、標準のJavaScriptコードでは、つぎのように回数を納める変数(count)がなければなりません。そしてこの変数は、別のコードから書き替えることもできてしまいます[*1]。ボタンをクリックするたびに回数は1ずつ加算されますので、動きは問題ありません。
標準JavaScriptlet count = 0; const button = document.querySelector('button'); button.addEventListener('click', (event) => console.log(`count: ${++count}`));
関数型プログラミングでは、処理を純粋な関数で加えてゆきます(「純粋関数について」参照)。オペレータscan()
は、第1引数に渡したコールバックの戻り値をつぎの引数に渡します。第2引数(0)ははじめのコールバックが受け取る初期値です。Array.reduce()
メソッドと考え方は同じです。つぎのRxJSのコードで、ボタンクリックの回数(count)がカウントアップされます。そして、回数は引数で渡されるので、外からは参照できません。
RxJSconst {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コードでも、回数の値を外からは隠せます。つぎのように、イベントリスナーを即時関数で定め、回数は引数で与えればよいのです。ただし、わかりやすいコードとはいえなくなります。
標準JavaScriptbutton.addEventListener('click',((count) => (event) => console.log(`count: ${++count}`) )(0));
05 イベントの流れを操作する
RxJSのオペーレータ(関数)は、イベントの流れをObservable
によりさまざまに操作できます。前項のコードで、ボタンクリックの処理はつづけざまに受けつけず、一定の時間(1秒間)は無視することにしましょう。標準のJavaScriptコードでは、つぎのように前回処理した時間を変数(lastClick)で覚えておかなければなりません。補助的な変数(rate)も増えます。ブラウザのコンソールにはクリックした回数のほか間隔秒数も示されますので、処理時間が1秒以上開いているのを確かめられるでしょう。
標準JavaScriptlet 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()
オペレータが引数のミリ秒数の間、値(イベント)の出力を止めます。このメソッドを、つぎのように処理がはじまる前に差し込んでしまえばよいのです。時間はオペレータの引数に与えるだけですから、別に変数にとっておかなくて済みます。
RxJSconst {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)を減らすことにしましょう。
標準JavaScriptlet 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でお確かめください。
RxJSconst {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入門
- RxJS 6入門 02: Observable
- RxJS 6入門 03: Observer
- RxJS 6入門 04: Subscription
- RxJS 6入門 05: Subject
- RxJS 6入門 06: Observableをつくる関数とオペレータ
- RxJS 6入門 07: Scheduler
作成者: 野中文雄
更新日: 2018年10月7日 公式サイトのディレクトリ再編にもとづつくリンクと本文の一部記述の修正。
作成日: 2018年5月28日
Copyright © 2001-2018 Fumio Nonaka. All rights reserved.