特定の副作用のために新しいサブスクリプションを作成する必要があるのはいつですか?


10

先週、私はRxJSに答えました 質問「特定の副作用ごとにサブスクリプションを作成するべきか、それともサブスクリプションを最小限に抑えるべきか」について、別のコミュニティメンバーと話し合いました。完全なリアクティブアプリケーションアプローチの観点からどの方法を使用するか、またはいつ切り替えるかを知りたい。これは私とおそらく他の人たちが不必要な議論を避けるのに役立ちます。

セットアップ情報

  • すべての例はTypeScriptにあります
  • サブスクリプションのライフサイクル/コンストラクターの使用に関する質問に焦点を当てず、フレームワークを無関係に保つ
    • 想像してみてください:サブスクリプションはコンストラクター/ライフサイクルの初期化で追加されます
    • 想像してみてください:登録解除はライフサイクル破棄で行われます

副作用とは(角度サンプル)

  • UIでの更新/入力(例 value$ | async
  • コンポーネントの出力/アップストリーム(例@Output event = event$
  • 異なる階層の異なるサービス間の相互作用

使用例:

  • 2つの機能: foo: () => void; bar: (arg: any) => void
  • 2つのソースオブザーバブル: http$: Observable<any>; click$: Observable<void>
  • foohttp$発行された後に呼び出され、値は必要ありません
  • barclick$エミット後に呼び出されますが、現在の値が必要ですhttp$

ケース:特定の副作用ごとにサブスクリプションを作成する

const foo$ = http$.pipe(
  mapTo(void 0)
);

const bar$ = http$.pipe(
  switchMap(httpValue => click$.pipe(
    mapTo(httpValue)
  )
);

foo$.subscribe(foo);
bar$.subscribe(bar);

ケース:サブスクリプションを最小限に抑える

http$.pipe(
  tap(() => foo()),
  switchMap(httpValue => click$.pipe(
    mapTo(httpValue )
  )
).subscribe(bar);

要するに私自身の意見

たとえば、サブスクライバーがパイプにどのように影響するか(たとえば、オブザーバブルを共有するかどうか)を考える必要があるため、サブスクリプションが最初にRxランドスケープをより複雑にするという事実を理解できます。ただし、コードを分離すればするほど(集中するほど、何が起こるか)、将来のコードの保守(テスト、デバッグ、更新)が容易になります。そのことを念頭に置いて、コード内の任意の副作用に対して、常に1つの監視可能なソースと1つのサブスクリプションを作成します。2つ以上の副作用がまったく同じソースオブザーバブルによってトリガーされた場合、オブザーバブルを共有し、ライフサイクルが異なる可能性があるため、各副作用を個別にサブスクライブします。

回答:


6

RxJSは非同期操作を管理するための貴重なリソースであり、可能な場合はコードを単純化する(サブスクリプションの数を減らすことを含む)ために使用する必要があります。同様に、RxJSがアプリケーションのサブスクリプションの総数を削減できるソリューションを提供している場合、オブザーバブルの後にそのオブザーバブルへのサブスクリプションが自動的に続くべきではありません。

ただし、厳密に「必要」ではないサブスクリプションを作成することが有益な場合があります。

例外の例-単一のテンプレートでのオブザーバブルの再利用

最初の例を見てください。

// Component:

this.value$ = this.store$.pipe(select(selectValue));

// Template:

<div>{{value$ | async}}</div>

テンプレートでvalue $が1回だけ使用される場合、非同期パイプと、コードエコノミーと自動サブスクリプション解除に対するその利点を利用します。ただし、この回答に従って、テンプレート内の同じ非同期変数への複数の参照は避けてください。たとえば、

// It works, but don't do this...

<ul *ngIf="value$ | async">
    <li *ngFor="let val of value$ | async">{{val}}</li>
</ul>

この状況では、代わりに別のサブスクリプションを作成し、これを使用してコンポーネントの非同期変数を更新します。

// Component

valueSub: Subscription;
value: number[];

ngOnInit() {
    this.valueSub = this.store$.pipe(select(selectValue)).subscribe(response => this.value = response);
}

ngOnDestroy() {
    this.valueSub.unsubscribe();
}

// Template

<ul *ngIf="value">
    <li *ngFor="let val of value">{{val}}</li>
</ul>

技術的には、を使用せずvalueSubに同じ結果を得ることができますが、アプリケーションの要件により、これは正しい選択です。

サブスクライブするかどうかを決定する前にオブザーバブルの役割と寿命を検討する

2つ以上のオブザーバブルを一緒に使用する場合にのみ使用する場合は、適切なRxJSオペレーターを使用して、それらを1つのサブスクリプションに結合する必要があります。

同様に、first()がオブザーバブルの最初のエミッションを除くすべてを除外するために使用されている場合、継続的な役割を持つオブザーバブルよりも、コードを節約して「余分な」サブスクリプションを回避する大きな理由があると思いますセッション。

個々のオブザーバブルのいずれかが他とは無関係に役立つ場合、個別のサブスクリプションの柔軟性と明快さを検討する価値があります。しかし、私の最初の声明のとおり、明確な理由がない限り、すべてのオブザーバブルに対してサブスクリプションが自動的に作成されるべきではありません。

退会について:

追加のサブスクリプションに対するポイントは、より多くのサブスクリプション解除が必要になることです。あなたが言ったように、私たちは必要なすべてのサブスクリプション解除がDestroyに適用されると仮定したいと思いますが、実際の生活は常にスムーズに進むとは限りません!繰り返しますが、RxJSはこのプロセスを合理化するための便利なツール(たとえばfirst())を提供します。この記事では、関連する詳細情報と例を提供します。

個人の好み/冗長性と簡潔さ:

自分の好みを考慮に入れてください。コードの冗長性に関する一般的な議論に迷惑をかけたくありませんが、目的は、過度の「ノイズ」とコードを過度に暗号化することの間の適切なバランスを見つけることです。これは一見の価値があるかもしれません


まずは詳細な回答ありがとうございます!#1に関して:私の観点からは、非同期パイプはサブスクリプション/副作用でもあり、ディレクティブ内でマスクされているだけです。#2については、コードサンプルを追加してください。正確に言っていることはわかりません。サブスクリプションの解除について:ngOnDestroy以外の場所でサブスクリプションのサブスクリプションを解除する必要があるのは、これまでにありません。First()はデフォルトではサブスクライブを実際に管理しません。0は=コンポーネントが破棄されますが、サブスクリプションオープンを発行します。
Jonathan Stellwag

1
ポイント2は、すべてのオブザーバブルをサブスクリプションで自動的に追跡するのではなく、サブスクリプションもセットアップするかどうかを決定するときに、各オブザーバブルの役割を検討することでした。この点について、medium.com/@benlesh/rxjs-dont-unsubscribe-6753ed4fda87を確認することをお勧めします。「サブスクリプションオブジェクトが多すぎると、サブスクリプションを強制的に管理しているため、アドバンテージを活用できません。 Rxの力の。」
マットサンダース

1
@JonathanStellwagに感謝します。info REコールバックもありがとう-アプリでは、これを防ぐために(たとえば、first()が使用されている場合でも)すべてのサブスクリプションを明示的にサブスクライブ解除しますか?
マットサンダース

1
はい、そうです。現在のプロジェクトでは、常にサブスクリプションを解除するだけで、ハングしているすべてのノードの約30%を最小化しました。
Jonathan Stellwag

2
登録解除の私の好みtakeUntilはから呼び出される関数との組み合わせngOnDestroyです。これをパイプに追加するのが1つのライナーですtakeUntil(componentDestroyed(this))stackoverflow.com/a/60223749/5367916
カートハミルトン

2

サブスクリプションの最適化が最終目的である場合は、論理的な極端に行き、次の一般的なパターンに従ってください。

 const obs1$ = src1$.pipe(tap(effect1))
 const obs2$ = src2$pipe(tap(effect2))
 merge(obs1$, obs2$).subscribe()

タップで排他的に副作用を実行し、マージでアクティブ化すると、サブスクリプションが1つだけになります。

これを行わない理由の1つは、RxJSの有用性の多くを無効にしていることです。これは、監視可能なストリームを作成し、必要に応じてストリームをサブスクライブ/サブスクライブ解除する機能です。

あなたのオブザーバブルは論理的に構成されるべきであり、サブスクリプションを減らすという名のもとに汚染されたり混乱したりしてはいけないと私は主張します。fooエフェクトは論理的にbarエフェクトと結合する必要がありますか?一方は他方を必要としますか?http $が発行されたときにfooをトリガーしたくないのでしょうか?関連のない機能間で不要な結合を作成していますか?これらはすべて、1つのストリームに入れないようにする理由です。

これはすべて、複数のサブスクリプションIMOで管理しやすいエラー処理を考慮してさえいない


お返事ありがとうございます。一つしか答えられないのが残念です。あなたの答えは@Matt Saundersが書いたものと同じです。その別の視点。マットの努力のおかげで私は彼に受け入れを与えました。あなたが私を許してくれることを願っています:)
ジョナサン・ステルワグ
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.