RxJSのメリットや使いどころについて解説するよ

Angular入門コースの受講者さんから、
Peing経由でRxJSについての質問が届きました。

RxJSへの質問

はじめまして、udemyのangular講座を受講しています。
「RxJSを理解しよう」節の追加ありがとうございます。
リアクティブとは?の説明の「リアクションして」の部分が、
これまでの非同期のコールバックとどう違うのか?が
いまいち良く理解できておりません。
例えば、https://qiita.com/hkusu/items/78fbf31051e6ee131731で
RxJSを使う場合と使わない場合で
ただ単にコードが複雑になってるだけのような気もします。
使うと何か良いのか?使わない場合に何ができるようになるのか?
とか、どうゆう場面で使うのか?あたりと
もう少しつっこんで頂けると嬉しかったかもしれないです。
実際どうなのでしょう?

質問ありがとうございます!
RxJSは非同期処理をうまく管理するためのライブラリですが、
仕組みの理解は非常に難しいです、はっきり言って。

UdemyのAngularコースでは初心者を対象としているので、
「Angularが依存しているので避けて通れないので、これだけは覚えてください」程度の解説で、
そこまで深く説明できていなかったのですが、
わかりますその気持ち。

ということで、質問について解説します!

RxJSを使うと何が嬉しいのか

質問者さんからQiitaの参考記事のURLを書いてもらいましたが、
そこでは単純にAPIを叩く想定で、RxJSを使う場合とPromiseで処理する場合の例が紹介されています。

しかし残念ながら、この例はRxJSの利点が全然わからないやつです。
おっしゃる通り、複雑化してるだけですね。

じゃあ、RxJSの何が良いのか。

ぼくは非同期処理をスッキリかけるのがRxJSのメリットだと思います。

ひとつ例を挙げてみましょう。
社員管理アプリケーション(公式Tour of Heroes)の実装例を見てみます。

import { Observable } from 'rxjs/Observable';
import { Subject } from 'rxjs/Subject';
import 'rxjs/add/observable/of';
import 'rxjs/add/operator/catch';
import 'rxjs/add/operator/debounceTime';
import 'rxjs/add/operator/distinctUntilChanged’;

@Component(...)
export class MemberSearchComponent implements OnInit {
  members: Observable<Member[]>;
  private searchTerms = new Subject<string>();

  ngOnInit(): void {
    this.members = this.searchTerms
      .debounceTime(300) // 最後のイベントから300ms待つ
      .distinctUntilChanged() // 文字列が変更されたときだけ処理を渡す
      .switchMap(term => term ? // 最新のサービスの呼び出し
        this.memberSearchService.search(term) :
        Observable.of<Member[]>([])
      )
      .catch(error => {
        console.log(error);
        return Observable.of<Member[]>([]);
      });
  }
}

これは、検索ボックスに文字を入力した際にAPIからデータを取得するコンポーネントです。
(member-search.component.ts)

これをRxJSを使わずに書き直すと次のようになります。
(わかりやすくaxiosを利用してます)

import axios from 'axios';
import { debounce } from 'throttle-debounce';

// axios settings
axios.defaults.baseURL = 'http://qiita.com/api/v2';
const CancelToken = axios.CancelToken;
let cancel;

export class MemberSearchComponent implements OnInit {

  members: Member[] = []; // 取得データ
  searchText = ''; // 検索テキスト
  cachedText = ''; // 前回の検索テキスト(キャッシュ)
  isFetching = false; // 通信中かのフラグ
    
  constructor() {
    this.onKeyup = debounce(300, this.onKeyup); // debounceTime
  }

  ngOnInit(): void { }

  onKeyup(event): void {

    this.searchText = event.target.value;
    if (this.searchText === this.cachedText) return; // distinctUntilChanged
    
    if (this.isFetching) cancel(); // switchMap
    
    axios.get('/items?per_page=10&query=' + this.searchText, {
      transformRequest: [(data, headers) => {
        this.isFetching = true; // 送信前にフラグをON
        return data;
      }],
      cancelToken: new CancelToken(function executor(c) {
        cancel = c;
      })
    })
      .then(res => {
        this.cachedText = this.searchText;
        // do something
        this.members = res.data;
        this.isFetching = false;
      })
      .catch(error => {
        if (axios.isCancel(error)) {
          console.warn('cancel fetch');
        } else {
          console.log(error);
        }
    
        this.isFetching = false;
      });
  }
}

いかがでしょうか。
眺めるだけで吐き気がしますね

コードのスッキリ加減は一目瞭然。
どんだけスッキリしてんだよって感じです。
なので、RxJSを使わない場合、どうなるかはすぐにお分かりいただけると思います。
(スパゲッティ 一直線ですよね。)

今回はQiitaの参考記事でaxiosを使っていたので合わせましたが、適宜fetch$.ajaxなどに読み換えてください。

RxJSの使いどころ

RxJSは小規模なサイト構築でももちろん使えますが、
やはりSPAみたいな非同期処理がいくつも飛び交うようなアプリケーションなどで真価を発揮します。

ぼくの経験上、非同期処理のハンドリングがコードの見通しを複雑化するんですね。
async, awaitパターンを使っても、まぁまぁ散らかる印象があります。
似たようなコールバックがいっぱいだよぉ、、みたいな。

そういう複雑なときほど、RxJSの書き方が一番スッキリ書けると実感しています。
というわけで、AngularでRxJSが採用されているるのは、とても秀逸なことなのです。

RxJSはオペレータが種類豊富で便利

レクチャーの中で、RxJSを、

「RxJS」はリアクティブプログラミングを行えるライブラリ。
リアクティブプログラミングとは、通知されてくるデータを受け取るたびに関連したプログラムが反応し(リアクション)して、処理を行うようにするプログラミングの考え方です。

と解説したのは、RxJSの「オペレータ」と呼ばれる仕組みのことで、
渡ってくるデータを途中で整形する機能のことを指して、リアクティブプログラミングを行うと説明しました。

このオペレータという機能を連続的に行うことができるので、
リアクティブにデータを整形できるのだとぼくは認識しています。

    this.members = this.searchTerms
      .debounceTime(300) // 最後のイベントから300ms待つ
      .distinctUntilChanged() // 文字列が変更されたときだけ処理を渡す
      .switchMap(term => term ? // 最新のサービスの呼び出し
        this.memberSearchService.search(term) :
        Observable.of<Member[]>([])
      )
      .catch(error => {
        console.log(error);
        return Observable.of<Member[]>([]);
      });

このドットでつなげられるパターンが、複雑な処理を行うようになってくると威力を発揮してくるんですよね。
ドットによって各処理が分離されているので、視覚的にも流れがわかりやすいです。

そして、RxJSのオペレータは使い切れないほど用意されているので、
特に困ることはないはず。

オペレータの種類に関しては、レクチャー内では必要最低限だけであまり紹介していません。
@wakamshaさんの記事がわかりやすいので、一度目を通してみることをおすすめします!
RxJS を学ぼう #2 – よく使う ( と思う ) オペレータ15選
RxJS を学ぼう #3 – 知ってると便利な ( かもしれない ) オペレータ 8選

公式: Observable | RxJS API Document

最後に

質問への回答は以上です。

RxJS難しいですよね。。ぼくもまだ説明するのがしっかりできてないです。
レクチャー動画の方はもう少しわかりやすい説明をつけ加えますね。

まさかPeingから技術的な質問が来るとは思っていなかったんですが、
匿名で質問したい方も当然いらっしゃいますよね。

質問は随時受け付けていますので、
受講生のかたはQ&Aやメッセージ、または
TwitterPeingからも受け付けているので、気軽に質問してください。

引き続きAngularを一緒に楽しみましょう!
出来る限り質問にはお答えしたいと思います。

※質問による回答は、今回のようにブログで行うこともあります。

更新情報はTwitterでも配信しています