はじめに
Observable
をsubscribe
して、その中でAPIの呼び出しなどを行う処理を書くことがあるかと思う。自分の実装方法が悪いのかもしれないが、無駄にAPIが呼びされてしまっているケースがよく見受けられる。console.log()
で出力すると、エグいことになっていることもある…
hoge$.subscribe((hoge: Hoge) => {
this.hogeService.callApi(hoge.fuga);
});
何がしたいのか
パフォーマンスの低下にも繋がるし、やはり無駄な処理は極力実行されないようにしたい。特にやりたいのが、「現在の状態」と「過去や未来の状態」を比較した上で、必要な時にだけ処理が実行されるようにしたい。
※Reactでは、当たり前のようにそのような機能が備わっていた。
Reactの場合
reactの場合1: setState()
コンポーネントのstateを更新し、ビューを更新するためのメソッド。
setState((prevState, props) => {
// prevState.hoge === props.hoge 比較可能
});
reactの場合2: Lifecycleメソッド
Lifecycleメソッドも考慮済み。以下は、ライフサイクルメソッドのひとつで、コンポーネントがアップデートされる直前で呼び出されるcomponentWillUpdate()。
componentWillUpdate(nextProps, nextState) {
// nextProps.hoge === this.props.hoge 比較可能
// nextState.hoge === this.state.hoge 比較可能
};
reactの場合3: shouldComponentUpdate()
その名も「コンポーネントをアップデートすべき」。falseを返せば、コンポーネントの更新を停止できる。
shouldComponentUpdate(nextProps, nextState) {
// nextProps.hoge === this.props.hoge 比較可能
// nextState.hoge === this.state.hoge 比較可能
}
Angularの場合
Angularの場合はどうする?
Lifecycle hooksには、新旧の状態を比較するような機能はなさそう。そもそもRxJS側の話でもある。
ということで、RxJSでReactと同じようなことができないか調べてみた。すると良さそうなRxJSのオペレータがあった。
distinctUntilChanged
distinctUntilChanged
Reactにもあった新旧の状態を比較するためのもの。true
を返せばストリームを止められる。(1回目は自動的に出力)
hoge$
.pipe(
distinctUntilChanged((prev, current) => {
// prev === current 比較可能
}),
)
.subscribe(...);
実装例
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のオブジェクトの比較
以下は別物とみなされる。
{ a: 'a' } === { a: 'a' } // => false
===
はメモリアドレスの比較であり、{ a: 'a' }
は new Object(…)
という意味で、それぞれ新しくオブジェクトを生成しており、別のメモリアドレスに保存されるから。
注意(続き)
JavaScriptのオブジェクトの比較
オブジェクトを比較するには、値同士を比較したり、文字列に変換したりするなど方法は色々考えられる。
const aaa = { a: 'a' }
aaa.a === aaa.a // => true
JSON.stringify({ a: 'a' }) === JSON.stringify({ a: 'a' }) // => true
その他に使えそうなもの1
distinct
オペレータ
前後関係なく、比較する対象に重複したものがあれば、すべてtrue
となり、処理を停止できる。
distinctUntilKeyChanged
オペレータ
distinctUntilChanged
と同様、新旧の状態を比較するが、比較対象としてkey
を指定できる。
その他に使えそうなもの2
switchMap
オペレータ
受け取った値から別のObservableを生成し、元々のObservableに格納しつつ、元々のストリームを破棄する。
pairwise
オペレータ
distinctUntilChanged
と同じようなもの。こちらは新旧の状態を配列で受け取る。
まとめ
これが正解かどうかはわからない。他に良い方法があれば教えてください。
Angularを始めてから約2ヶ月。特にRxJSは奥が深くて、わかっているようでまだよくわかっていない部分が多々ある。RxJSを使いこなせるようになるには、いろいろハマってもっともっと試行錯誤していく必要があると思う今日この頃。