Коли я повинен створити нову підписку для конкретного побічного ефекту?


10

Минулого тижня я відповів на запитання RxJS, де потрапив на дискусію з іншим членом спільноти з приводу: "Чи варто створити підписку на кожен конкретний побічний ефект чи слід намагатися мінімізувати підписки взагалі?" Я хочу знати, яку методологію використовувати в умовах повного підходу до реактивного застосування або коли переходити з одного на інший. Це допоможе мені і, можливо, іншим уникнути непотрібних дискусій.

Інформація про налаштування

  • Усі приклади є у TypeScript
  • Для кращого зосередження уваги на запитанні без використання життєвих циклів / конструкторів для передплати, а також залишатись у рамках не пов'язаними
    • Уявіть: підписки додаються в init конструктора / життєвого циклу
    • Уявіть собі: скасування підписки робиться в процесі знищення життєвого циклу

Що таке побічний ефект (кутовий зразок)

  • Оновлення / введення в інтерфейсі (наприклад value$ | async)
  • Вихід / вихідний компонент (наприклад @Output event = event$)
  • Взаємодія між різними службами в різних ієрархіях

Зразковий футляр:

  • Дві функції: foo: () => void; bar: (arg: any) => void
  • Дві джерела спостереження: http$: Observable<any>; click$: Observable<void>
  • fooвикликається після того, http$як викинуто і не потребує значення
  • barвикликається після click$випромінювання, але потребує поточного значення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, тому що ви повинні подумати про те, як підписники повинні впливати на трубу чи не, наприклад (поділитися вашим спостережуваним чи ні). Але чим більше ви розділяєте свій код (тим більше ви зосереджуєтесь: що відбувається, коли), тим простіше підтримувати (перевірити, налагодити, оновити) свій код у майбутньому. Зважаючи на це, я завжди створюю єдине спостережуване джерело та одну підписку для будь-яких побічних ефектів у своєму коді. Якщо два або більше побічних ефектів, які я маю, викликані саме таким джерелом, який можна спостерігати, я поділяю свої спостережувані та підписуюсь на кожен побічний ефект окремо, оскільки він може мати різні життєві цикли.

Відповіді:


6

RxJS є цінним ресурсом для управління асинхронними операціями, і його слід використовувати для спрощення коду (включаючи зменшення кількості підписок), де це можливо. Так само за спостережуваним не слід автоматично супроводжуватися підпискою на цю спостережувану, якщо RxJS надає рішення, яке може зменшити загальну кількість підписок у вашій програмі.

Однак є ситуації, коли може бути вигідним створити підписку, яка не є суворо "необхідною":

Приклад винятку - повторне використання спостережуваних даних в одному шаблоні

Дивлячись на ваш перший приклад:

// Component:

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

// Template:

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

Якщо значення $ використовується лише один раз у шаблоні, я б скористався асинхронною трубкою та її перевагами для економії коду та автоматичної підписки. Однак відповідно до цієї відповіді слід уникати декількох посилань на одну і ту ж змінну асинхронізації в шаблоні, наприклад:

// 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, але вимоги програми означають, що це правильний вибір.

Враховуючи роль і тривалість спостережуваного, перш ніж приймати рішення про підписку

Якщо дві або більше спостережуваних даних використовуються лише тоді, коли вони разом узяті, для об'єднання їх в одну підписку слід використовувати відповідні оператори RxJS.

Аналогічно, якщо first () використовується для фільтрації всіх, окрім першої емісії спостережуваного, я думаю, що є більший привід бути економним зі своїм кодом та уникати "зайвих" підписок, ніж для спостережуваного, який виконує постійну роль у сесія.

Якщо будь-яка з окремих спостережень корисна незалежно від інших, гнучкість та чіткість окремих підписок (ів), можливо, варто врахувати. Але відповідно до мого початкового твердження, підписка не повинна створюватися автоматично для кожного спостережуваного, якщо тільки немає чіткої причини для цього.

Щодо скасування підписок:

Точка проти додаткових підписок є те , що всі більше скасовує підписки потрібно. Як ви вже говорили, ми хотіли б припустити, що всі необхідні відписки застосовуються на Deestroy, але реальне життя не завжди йде так гладко! Знову ж таки, RxJS надає корисні інструменти (наприклад, перший () ) для впорядкування цього процесу, що спрощує код і зменшує потенціал для витоку пам'яті. Ця стаття надає релевантну додаткову інформацію та приклади, які можуть бути корисними.

Особисті переваги / багатослівність проти терміновості:

Враховуйте власні переваги. Я не хочу відхилятися від загальної дискусії про багатослівність коду, але метою має бути пошук правильного балансу між занадто великим «шумом» і тим, щоб зробити ваш код надто крикливим. Це, можливо, варто подивитися .


Спочатку дякую за детальну відповідь! Щодо №1: з моєї точки зору, асинхронна труба - це також підписка / побічний ефект, просто що вона замаскована всередині директиви. Щодо №2, чи можете ви додати трохи зразка коду, я не розумію, що саме ви мені говорите. Щодо скасування передплати: я ніколи раніше не мав цього, передплату потрібно відписати в будь-якому іншому місці, крім ngOnDestroy. First () насправді не керує підпискою для вас за замовчуванням: 0 emits = Підписка відкрита, хоча компонент знищений.
Джонатан Стеллуг

1
Пункт 2 справді стосувався лише розгляду ролі кожного спостерігаючого при вирішенні питання про те, чи встановлювати підписку, а не автоматично слідкувати за кожним спостережуваним підпискою. З цього приводу я б запропонував поглянути на medium.com/@benlesh/rxjs-dont-unsubscribe-6753ed4fda87 , де сказано: "Зберігання занадто багато об'єктів передплати - це знак, що ви керуєте своїми підписками обов'язково, а не користуєтесь ними сили Rx ".
Метт Сондерс

1
Вдячний завдяки @JonathanStellwag. Дякую також за інформаційні зворотні дзвінки RE - у своїх програмах ви явно скасовуєте підписку на кожну підписку (навіть там, де використовується перший (), наприклад), щоб запобігти цьому?
Метт Сондерс,

1
Так. У поточному проекті ми мінімізували близько 30% усіх вузлів, що звисали, просто відписуючись завжди.
Jonathan Stellwag

2
Мої переваги для скасування передплати takeUntilпоєднуються з функцією, від якої викликається ngOnDestroy. Це один лайнер , який додає це до труби: 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()

Виключно виконання побічних ефектів під час натискання та активації злиття означає, що у вас є лише одна підписка.

Однією з причин цього не робити є те, що ви втрачаєте багато з того, що робить RxJS корисним. Яка здатність складати спостережувані потоки та передплачувати / скасовувати підписку на потоки за потребою.

Я заперечую, що ваші спостереження повинні складатись логічно, а не забруднювати чи не плутати в ім'я зменшення кількості підписок. Чи слід логічно поєднувати ефект foo з ефектом смуги? Чи один вимагає іншого? Чи я колись, можливо, не захочу триггер foo, коли http $ випускає? Чи створюю я непотрібну зв'язок між непов'язаними функціями? Це все причини, щоб не ставити їх в один потік.

Це все, навіть не враховуючи обробку помилок, якою легше керувати за допомогою декількох IMO підписок


Спасибі за вашу відповідь. Мені шкода, що я можу прийняти лише одну відповідь. Ваша відповідь така ж, як і написана @Matt Saunders. Це просто інша точка зору. Через зусилля Метта я дав йому згоду. Сподіваюся, ви можете мені пробачити :)
Джонатан Стеллуг,
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.