RxJS:登録解除しないでください

こんにちは、Habr! Ben Leshの記事「 RxJS:Do n't Unsubscribe 」の翻訳を紹介します。



ええと...大丈夫、ただ購読を拒否しないでください。



多くの場合、多くの非同期コードを運ぶアプリケーションの構造化など、RxJSコードに関する問題のデバッグを支援します。 同時に、私はいつも人々がプロセッサーを大量のサブスクリプションで維持しているのと同じことを見ています。 開発者はObservableを使用して3つのHTTPリクエストを作成し、何らかのイベントが発生したときに呼び出される3つのサブスクリプションオブジェクトを格納します。



なぜこれが起こっているのか知っています。 人々は「addEventListener」をN回使用することに慣れており、不要になったら「removeEventListener」をN回呼び出します。 サブスクリプションオブジェクトでも同じことを行うのが自然であり、ほとんどの場合は正しいでしょう。 しかし、より良い方法があります。 あまりにも多くのサブスクリプションオブジェクトを保存しているということは、サブスクリプションを強制的に管理し、Rxを活用していないことを示しています。



必須のサブスクリプション管理はどのようなものか



たとえば、次の架空のコンポーネントを取り上げます(これは具体的にはReactやAngularではなく、単なる一般的な例です)。



class MyGenericComponent extends SomeFrameworkComponent { updateData(data) { // -     } onMount() { this.dataSub = this.getData() .subscribe(data => this.updateData(data)); const cancelBtn = this.element.querySelector('.cancel-button'); const rangeSelector = this.element.querySelector('.rangeSelector'); this.cancelSub = Observable.fromEvent(cancelBtn, 'click') .subscribe(() => { this.dataSub.unsubscribe(); }); this.rangeSub = Observable.fromEvent(rangeSelector, 'change') .map(e => e.target.value) .subscribe((value) => { if (+value > 500) { this.dataSub.unsubscribe(); } }); } onUnmount() { this.dataSub.unsubscribe(); this.cancelSub.unsubscribe(); this.rangeSub.unsubscribe(); } }
      
      





上記の例では、 `onUnmount()`メソッドの3つのサブスクリプションオブジェクトで `unsubscribe`を手動で呼び出すことがわかります。 また、ユーザーが15行目と22行目のキャンセルボタンを押したとき、またはデータストリームを停止するしきい値である範囲セレクターを500以上に設定したときに、「this.dataSub.unsubscribe()」を呼び出します。 (理由はわかりませんが、これは単なる奇妙なコンポーネントです)。



このアプローチの欠点は、このかなり単純な例のいくつかの場所でサブスクリプションを手動で管理することです。



このアプローチを使用する唯一の本当の利点はパフォーマンスです。 仕事をするために使用する抽象化が少ないので、これは少し良くなる可能性があります。 ただし、ほとんどのWebアプリケーションでこれが顕著な効果をもたらす可能性は低いため、心配する価値はないと思います。



さらに、複数のサブスクリプションをいつでも1つに結合して、親サブスクリプションを作成し、他のすべてを子として追加できます。 ただし、基本的には同じことを行います。



takeUntilでサブスクリプション管理を構築する



次に、同じ例を実行しますが、RxJSの `takeUntil`演算子を使用します。



 class MyGenericComponent extends SomeFrameworkComponent { updateData(data) { // do something framework-specific to update your component here. } onMount() { const data$ = this.getData(); const cancelBtn = this.element.querySelector('.cancel-button'); const rangeSelector = this.element.querySelector('.rangeSelector'); const cancel$ = Observable.fromEvent(cancelBtn, 'click'); const range$ = Observable.fromEvent(rangeSelector, 'change') .map(e => e.target.value); const stop$ = Observable.merge(cancel$, range$.filter(x => x > 500)) this.subscription = data$.takeUntil(stop$) .subscribe(data => this.updateData(data)); } onUnmount() { this.subscription.unsubscribe(); } }
      
      





最初に気付くのは、コードが少ないことです。 しかし、これは唯一の利点です。 ここで起こった別のことは、データストリームを停止するイベントを `stop $`ストリームに入れることです。 これは、たとえばタイマーなどでフローを停止するための別の条件を追加することに決めたらすぐに、新しい監視可能なオブジェクトを「stop $」に追加できることを意味します。 次に明白なことは、命令的に管理するサブスクリプションオブジェクトが1つしかないことです。 ここでは関数型プログラミングがオブジェクト指向の世界と交差するため、これを変更することはできません。 Javascriptは必須の言語であり、残りの世界を途中でやらなければなりません。



このアプローチのもう1つの利点は、監視対象オブジェクトを実際に完成させることです。 これは、いつでも処理できる完了イベントが発生することを意味します。 返されたサブスクリプションオブジェクトに対して単に「unsubscribe」を呼び出すと、サブスクリプションがキャンセルされたことが通知されません。 ただし、 `takeUntil`(または以下にリストされている他の演算子)を使用すると、observableが停止したことが完了ハンドラーを通じて通知されます。



私が言いたい最後の利点は、1つの場所でサブスクリプションを呼び出すことで、実際に「すべてをプラグイン」することです。これは、規律により、サブスクリプションを開始する場所を見つけやすくなるためです。コード。 監視対象オブジェクトは、サブスクライブするまで何もしないため、サブスクリプションポイントはコードの重要な部分です。



確かに、RxJSセマンティクスに関して1つの欠陥がありますが、他の利点を心配する価値はほとんどありません。 セマンティック上の欠陥は、観測されたオブジェクトの完成は、製造業者が消費者にジョブが完了したことを伝えたいという兆候であり、購読解除は消費者が製造者にもはやデータを必要としないことを伝えることです。



また、これと「unsubscribe」への単純な命令呼び出しの間には、非常に小さなパフォーマンスの違いがあります。 ただし、ほとんどのアプリケーションで目立つことはほとんどありません。



その他の演算子



Rx-wayでフローを停止する方法は他にもたくさんあります。 少なくとも次の演算子を確認することをお勧めします。



take(n):オブザーバブルを停止する前にN個の値を取ります。

takeWhile(述語):自身に対して渡された値を述語に対してチェックします; falseを返す場合、ストリームは完了します。

first():最初の値をスキップして終了します。

最初(述語):述語関数の各値をチェックし、trueを返す場合、スレッドは値をスキップして終了します。



概要:takeUntil、takeWhileなどを使用します。



RxJSサブスクリプションを管理するには、おそらく `takeUntil`などの演算子を使用する必要があります。 一般に、同じコンポーネントに2つ以上のサブスクリプションがあることがわかった場合、それらをより適切に定義できるかどうか疑問に思うはずです。





著者からRxJSについてもっと知りたいですか? rxworkshop.comにアクセスしてください!



All Articles