Kapan saya harus membuat Langganan baru untuk efek samping tertentu?

10

Minggu lalu saya menjawab pertanyaan RxJS di mana saya berdiskusi dengan anggota komunitas lain tentang: "Haruskah saya membuat langganan untuk setiap efek samping tertentu atau haruskah saya mencoba meminimalkan langganan secara umum?" Saya ingin tahu metodologi apa yang digunakan dalam hal pendekatan aplikasi reaktif penuh atau kapan harus beralih dari satu ke yang lain. Ini akan membantu saya dan mungkin orang lain untuk menghindari diskusi yang tidak penting.

Info pengaturan

  • Semua contoh dalam TypeScript
  • Untuk fokus yang lebih baik pada pertanyaan, jangan gunakan siklus hidup / konstruktor untuk langganan dan untuk tetap dalam kerangka yang tidak terkait
    • Bayangkan: Langganan ditambahkan dalam konstruktor / siklus hidup init
    • Bayangkan: Berhenti berlangganan dilakukan dalam menghancurkan siklus hidup

Apa itu efek samping (Sampel sudut)

  • Perbarui / Input di UI (mis. value$ | async)
  • Output / Hulu komponen (misalnya @Output event = event$)
  • Interacton antara layanan yang berbeda pada hierarki yang berbeda

Contoh penggunaan contoh:

  • Dua fungsi: foo: () => void; bar: (arg: any) => void
  • Dua sumber yang bisa diamati: http$: Observable<any>; click$: Observable<void>
  • foodipanggil setelah http$dipancarkan dan tidak memerlukan nilai
  • bardipanggil setelah click$dipancarkan, tetapi membutuhkan nilai saat ini darihttp$

Kasus: Buat langganan untuk setiap efek samping tertentu

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

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

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

Kasus: Minimalkan langganan secara umum

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

Pendapat saya sendiri singkatnya

Saya dapat memahami fakta bahwa langganan membuat lanskap Rx lebih rumit pada awalnya, karena Anda harus memikirkan bagaimana pelanggan harus memengaruhi pipa atau tidak misalnya (bagikan pengamatan Anda atau tidak). Tetapi semakin Anda memisahkan kode Anda (semakin Anda fokus: apa yang terjadi ketika) semakin mudah untuk mempertahankan (menguji, men-debug, memperbarui) kode Anda di masa depan. Dengan mengingat hal itu saya selalu membuat satu sumber yang dapat diamati dan satu langganan untuk setiap efek samping dalam kode saya. Jika dua atau lebih efek samping yang saya miliki dipicu oleh sumber yang sama persis yang dapat diamati, maka saya membagikan pengamatan saya dan berlangganan untuk setiap efek samping secara individual, karena dapat memiliki siklus hidup yang berbeda.

Jonathan Stellwag
sumber

Jawaban:

6

RxJS adalah sumber daya berharga untuk mengelola operasi asinkron dan harus digunakan untuk menyederhanakan kode Anda (termasuk mengurangi jumlah langganan) jika memungkinkan. Sama halnya, sebuah observable seharusnya tidak secara otomatis diikuti oleh berlangganan ke observable tersebut, jika RxJS memberikan solusi yang dapat mengurangi jumlah keseluruhan langganan dalam aplikasi Anda.

Namun, ada situasi di mana mungkin bermanfaat untuk membuat langganan yang tidak sepenuhnya 'diperlukan':

Contoh pengecualian - penggunaan kembali yang dapat diamati dalam satu templat

Melihat contoh pertama Anda:

// Component:

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

// Template:

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

Jika nilai $ hanya digunakan sekali dalam templat, saya akan mengambil keuntungan dari pipa async dan manfaatnya untuk penghematan kode dan berhenti berlangganan otomatis. Namun sesuai jawaban ini , beberapa referensi ke variabel async yang sama dalam sebuah template harus dihindari, misalnya:

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

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

Dalam situasi ini, saya akan membuat langganan terpisah dan menggunakan ini untuk memperbarui variabel non-async di komponen saya:

// 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>

Secara teknis, dimungkinkan untuk mencapai hasil yang sama tanpa valueSub, tetapi persyaratan aplikasi berarti ini adalah pilihan yang tepat.

Mempertimbangkan peran dan masa hidup yang dapat diamati sebelum memutuskan apakah akan berlangganan

Jika dua atau lebih yang dapat diamati hanya digunakan saat digunakan bersama-sama, operator RxJS yang sesuai harus digunakan untuk menggabungkannya menjadi satu langganan.

Demikian pula, jika first () digunakan untuk memfilter semua kecuali emisi pertama yang dapat diamati, saya pikir ada alasan yang lebih besar untuk menjadi ekonomis dengan kode Anda dan menghindari langganan 'ekstra', daripada untuk diamati yang memiliki peran berkelanjutan dalam sesi.

Jika ada di antara individu yang dapat diamati berguna secara terpisah dari yang lain, fleksibilitas dan kejelasan berlangganan terpisah mungkin perlu dipertimbangkan. Tetapi sesuai dengan pernyataan awal saya, langganan tidak boleh dibuat secara otomatis untuk setiap yang bisa diamati, kecuali ada alasan yang jelas untuk melakukannya.

Mengenai Berhenti Berlangganan:

Suatu titik terhadap langganan tambahan adalah bahwa lebih banyak langganan diperlukan. Seperti yang telah Anda katakan, kami ingin menganggap bahwa semua langganan yang diperlukan diterapkan pada Destroy, tetapi kehidupan nyata tidak selalu berjalan dengan lancar! Sekali lagi, RxJS menyediakan alat yang berguna (misalnya pertama () ) untuk merampingkan proses ini, yang menyederhanakan kode dan mengurangi potensi kebocoran memori. Artikel ini memberikan informasi dan contoh lebih lanjut yang relevan, yang mungkin bernilai.

Preferensi / verbositas pribadi vs kesederhanaan:

Pertimbangkan preferensi Anda sendiri. Saya tidak ingin menyimpang ke arah diskusi umum tentang verbositas kode, tetapi tujuannya adalah untuk menemukan keseimbangan yang tepat antara terlalu banyak 'noise' dan membuat kode Anda terlalu samar. Ini mungkin layak untuk dilihat .

Matt Saunders
sumber
Pertama terima kasih atas jawaban terinci Anda! Mengenai # 1: dari sudut pandang saya, pipa async juga merupakan efek langganan / samping, hanya saja itu ditutupi di dalam sebuah arahan. Mengenai # 2 dapatkah Anda menambahkan beberapa contoh kode, saya tidak mengerti maksud Anda dengan tepat. Mengenai Berhenti Berlangganan: Saya belum pernah memilikinya sebelum berlangganan harus berhenti berlangganan di tempat lain selain ngOnDestroy. Pertama () tidak benar-benar mengelola berhenti berlangganan untuk Anda secara default: 0 emits = berlangganan terbuka, meskipun komponen dihancurkan.
Jonathan Stellwag
1
Poin 2 benar-benar hanya tentang mempertimbangkan peran masing-masing yang dapat diamati ketika memutuskan apakah juga akan mengatur berlangganan, daripada secara otomatis mengikuti setiap yang dapat diamati dengan berlangganan. Pada titik ini, saya sarankan melihat medium.com/@benlesh/rxjs-dont-unsubscribe-6753ed4fda87 , yang mengatakan: "Menjaga terlalu banyak objek berlangganan adalah tanda Anda mengelola langganan secara imperatif, dan tidak memanfaatkan dari kekuatan Rx. "
Matt Saunders
1
Terima kasih kepada @JonathanStellwag. Terima kasih juga atas info RE callbacks - di aplikasi Anda, apakah Anda secara eksplisit berhenti berlangganan dari setiap langganan (bahkan di mana dulu () digunakan, misalnya), untuk mencegah hal ini?
Matt Saunders
1
Ya saya lakukan. Dalam proyek saat ini kami meminimalkan sekitar 30% dari semua node yang berkeliaran hanya dengan berhenti berlangganan.
Jonathan Stellwag
2
Preferensi saya untuk berhenti berlangganan adalah takeUntilkombinasi dengan fungsi yang dipanggil dari ngOnDestroy. Ini adalah salah satu kapal yang menambahkan ini ke pipa: takeUntil(componentDestroyed(this)). stackoverflow.com/a/60223749/5367916
Kurt Hamilton
2

jika mengoptimalkan langganan adalah tujuan akhir Anda, mengapa tidak pergi ke ekstrim logis dan ikuti saja pola umum ini:

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

Mengeksekusi efek samping secara eksklusif di ketuk dan mengaktifkan dengan penggabungan berarti Anda hanya memiliki satu langganan.

Salah satu alasan untuk tidak melakukan ini adalah karena Anda mensterilkan banyak hal yang membuat RxJS berguna. Yaitu kemampuan untuk menyusun aliran yang dapat diamati, dan berlangganan / berhenti berlangganan dari aliran sebagaimana diperlukan.

Saya berpendapat bahwa yang dapat diamati Anda harus disusun secara logis, dan tidak dicemari atau dikacaukan atas nama pengurangan langganan. Haruskah efek foo secara logis digabungkan dengan efek bar? Apakah yang satu membutuhkan yang lain? Apakah saya akan pernah tidak ingin memicu foo ketika http $ emit? Apakah saya membuat sambungan yang tidak perlu antara fungsi yang tidak terkait? Ini semua adalah alasan untuk menghindari menempatkan mereka dalam satu aliran.

Ini semua bahkan tidak mempertimbangkan penanganan kesalahan yang lebih mudah dikelola dengan beberapa langganan IMO

bryan60
sumber
Terima kasih atas jawaban Anda. Saya menyesal hanya bisa menerima satu jawaban. Jawaban Anda sama dengan jawaban tertulis oleh @Matt Saunders. Ini hanya sudut pandang lain. Karena upaya Matt, aku memberinya tanda terima. Saya harap Anda dapat memaafkan saya :)
Jonathan Stellwag