【RxJS】Subscribeの中で無駄な処理をさせないためには

# はじめに

`Observable`を`subscribe`して、その中でAPIの呼び出しなどを行う処理を書くことがあるかと思う。自分の実装方法が悪いのかもしれないが、無駄にAPIが呼びされてしまっているケースがよく見受けられる。`console.log()`で出力すると、エグいことになっていることもある…

```ts
hoge$.subscribe((hoge: Hoge) => {
  this.hogeService.callApi(hoge.fuga);
});
```

---

# 何がしたいのか

パフォーマンスの低下にも繋がるし、やはり無駄な処理は極力実行されないようにしたい。特にやりたいのが、**「現在の状態」と「過去や未来の状態」を比較した上で、必要な時にだけ処理が実行されるようにしたい**。

※Reactでは、当たり前のようにそのような機能が備わっていた。

---

# Reactの場合

---

# reactの場合1: setState()

コンポーネントのstateを更新し、ビューを更新するためのメソッド。

```js
setState((prevState, props) => {

  // prevState.hoge === props.hoge 比較可能

});
```

https://reactjs.org/docs/react-component.html#setstate

---

# reactの場合2: Lifecycleメソッド
Lifecycleメソッドも考慮済み。以下は、ライフサイクルメソッドのひとつで、コンポーネントがアップデートされる直前で呼び出されるcomponentWillUpdate()。

```js
componentWillUpdate(nextProps, nextState) {

  // nextProps.hoge === this.props.hoge 比較可能
  // nextState.hoge === this.state.hoge 比較可能

};
```

https://reactjs.org/docs/react-component.html#componentwillupdate

---

# reactの場合3: shouldComponentUpdate()

その名も「コンポーネントをアップデートすべき」。falseを返せば、コンポーネントの更新を停止できる。

```js
shouldComponentUpdate(nextProps, nextState) {

  // nextProps.hoge === this.props.hoge 比較可能
  // nextState.hoge === this.state.hoge 比較可能

}
```

https://reactjs.org/docs/react-component.html#shouldcomponentupdate

---

# Angularの場合

---

# Angularの場合はどうする？

Lifecycle hooksには、新旧の状態を比較するような機能はなさそう。そもそもRxJS側の話でもある。

ということで、RxJSでReactと同じようなことができないか調べてみた。すると良さそうなRxJSのオペレータがあった。

---

# distinctUntilChanged

---

# distinctUntilChanged

Reactにもあった新旧の状態を比較するためのもの。`true`を返せばストリームを止められる。（1回目は自動的に出力）

```ts
hoge$
  .pipe(
    distinctUntilChanged((prev, current) => {
      // prev === current 比較可能
    }),
  )
  .subscribe(...);
```
---

# 実装例

```ts
import { of } from 'rxjs';
import { distinctUntilChanged } from 'rxjs/operators';

interface Data {
  id: number;
  name: string;
};

of<Data>(
  { id: 1, name: 'hoge' },
  { id: 1, name: 'hoge' },
  { id: 2, name: 'fuga' },
  { id: 2, name: 'fuga' },
)
.pipe(
  distinctUntilChanged((prev: Data, current: Data) => {
    return prev.id === current.id;
  }),
)
.subscribe((data: Data) => this.hogeService.callApi(data.name));
```

結果: this.hogeService.callApi()の呼び出しは2回

---

# 注意

**JavaScriptのオブジェクトの比較**

以下は別物とみなされる。

```js
{ a: 'a' } === { a: 'a' } // => false
```

`===`はメモリアドレスの比較であり、`{ a: 'a'　}`は `new Object(…)` という意味で、それぞれ新しくオブジェクトを生成しており、別のメモリアドレスに保存されるから。

---

# 注意（続き）

**JavaScriptのオブジェクトの比較**

オブジェクトを比較するには、値同士を比較したり、文字列に変換したりするなど方法は色々考えられる。

```js
const aaa = { a: 'a' }
aaa.a === aaa.a // => true

JSON.stringify({ a: 'a' }) === JSON.stringify({ a: 'a' }) // => true
```

---

# その他に使えそうなもの1

**`distinct`オペレータ**

前後関係なく、比較する対象に重複したものがあれば、すべて`true`となり、処理を停止できる。

http://reactivex.io/rxjs/class/es6/Observable.js~Observable.html#instance-method-distinct

**`distinctUntilKeyChanged`オペレータ**

`distinctUntilChanged`と同様、新旧の状態を比較するが、比較対象として`key`を指定できる。

http://reactivex.io/rxjs/class/es6/Observable.js~Observable.html#instance-method-pairwise

---

# その他に使えそうなもの2

**`switchMap`オペレータ**

受け取った値から別のObservableを生成し、元々のObservableに格納しつつ、元々のストリームを破棄する。

http://reactivex.io/rxjs/class/es6/Observable.js~Observable.html#instance-method-switchMap

**`pairwise`オペレータ**

`distinctUntilChanged`と同じようなもの。こちらは新旧の状態を配列で受け取る。

http://reactivex.io/rxjs/class/es6/Observable.js~Observable.html#instance-method-pairwise

---

# まとめ

これが正解かどうかはわからない。他に良い方法があれば教えてください。

Angularを始めてから約2ヶ月。特にRxJSは奥が深くて、わかっているようでまだよくわかっていない部分が多々ある。RxJSを使いこなせるようになるには、いろいろハマってもっともっと試行錯誤していく必要があると思う今日この頃。