何时应针对特定副作用创建新的订阅?


10

上周,我回答了一个RxJS 问题,在那里我与另一位社区成员进行了讨论:“我应该为每种特定的副作用创建订阅,还是应该总体上减少订阅?” 我想知道在完整的反应式应用方法中使用什么方法,或者何时从一种方法切换到另一种方法。这将帮助我甚至其他人避免不必要的讨论。

设置信息

  • 所有示例均在TypeScript中
  • 为了更好地关注问题,不使用生命周期/构造函数进行订阅并保持框架无关
    • 想象一下:在构造函数/生命周期init中添加了订阅
    • 想象一下:取消订阅是在生命周期中完成的

什么是副作用(角度样本)

  • 用户界面中的更新/输入(例如value$ | async
  • 组件的输出/上游(例如@Output event = event$
  • 不同层次结构上不同服务之间的交互

示例用例:

  • 两个功能: foo: () => void; bar: (arg: any) => void
  • 两个源可观察到的: 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景观更加复杂的事实,因为您必须考虑订阅者应如何影响管道(例如,不影响管道)(共享或不观察)。但是,您分离代码的次数越多(关注的焦点越多:什么时候发生),将来维护(测试,调试,更新)代码越容易。考虑到这一点,我总是为我的代码中的任何副作用创建一个可观察的源和一个订阅。如果我的两个或更多副作用是由完全相同的可观察源触发的,则我共享我的可观察对象并分别订阅每个副作用,因为它可能具有不同的生命周期。

Answers:


6

RxJS是用于管理异步操作的宝贵资源,应该在可能的情况下用于简化代码(包括减少预订数量)。同样,如果RxJS提供了可以减少应用程序中订阅总数的解决方案,则不应自动在该可观察对象后跟该可观察对象的订阅。

但是,在某些情况下,创建并非严格“必需”的订阅可能会有所帮助:

示例异常-在单个模板中重用可观察对象

看你的第一个例子:

// Component:

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

// Template:

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

如果value $在模板中仅使用一次,我将利用异步管道及其在代码经济性和自动取消订阅方面的优势。但是,按照此答案,应避免在模板中多次引用同一异步变量,例如:

// 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()来过滤除可观察对象的第一个发射之外的所有内容,那么我认为,比起在其中持续发挥作用的可观察对象,有更大的理由来节省代码成本并避免“额外”订阅会议。

在任何单个可观察指标独立于其他可观察指标有用的情况下,可能值得考虑单独订阅的灵活性和明确性。但是按照我的最初声明,除非有明确的理由,否则不应为每个可观察的对象自动创建订阅。

关于退订:

一个点其他订购的是,更多的取消订阅要求。如您所说,我们想假设所有必要的取消订阅都适用于Destroy,但现实生活并不总是那么顺利!同样,RxJS提供了有用的工具(例如first())来简化此过程,从而简化了代码并减少了内存泄漏的可能性。本文提供了可能有价值的其他相关信息和示例。

个人偏好/冗长性与简洁性:

请考虑自己的喜好。我不想迷恋关于代码详细程度的一般性讨论,但目的应该是在过多的“噪音”与使您的代码过于神秘之间找到适当的平衡。这可能值得一看


首先感谢您的详细回答!关于#1:从我的角度来看,异步管道也是一种订阅/副作用,只是它在指令中被屏蔽了。关于#2,能否请您添加一些代码示例,我不明白您在说什么。关于取消订阅:在ngOnDestroy之外的任何其他地方都需要取消订阅,我从未有过。默认情况下,First()不会真正为您管理取消订阅:0发出=订阅已打开,尽管组件已销毁。
乔纳森·斯泰尔瓦格

1
第2点实际上是在决定是否还要设置订阅时考虑每个可观察对象的角色,而不是自动在每个可观察对象之后进行订阅。在这一点上,我建议您查看medium.com/@benlesh/rxjs-dont-unsubscribe-6753ed4fda87,其中说:“保持太多的订阅对象是您必须管理订阅的标志,并且没有利用的力量。”
马特·桑德斯

1
感谢@JonathanStellwag。也要感谢info RE回调-在您的应用中,您是否明确取消了每个订阅的订阅(例如,即使使用first()),也可以防止这种情况发生?
马特·桑德斯

1
是的,我愿意。在当前项目中,我们通过始终取消订阅来最大程度地减少了大约30%的所有节点徘徊。
Jonathan Stellwag

2
我偏爱取消订阅takeUntil的功能是与从调用的函数结合使用ngOnDestroy。这是一个将其添加到管道中的衬板:takeUntil(componentDestroyed(this))stackoverflow.com/a/60223749/5367916
Kurt Hamilton

2

如果优化订阅是您的最终选择,那为什么不走逻辑逻辑,而遵循以下一般模式:

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

在Tap中独家执行副作用并通过合并激活意味着您只有一个订阅。

不这样做的原因之一是,您正在使RxJS变得有用的很多东西。这是组合可观察流以及根据需要订阅/取消订阅流的能力。

我认为,您的可观察对象应该在逻辑上组成,不要以减少订阅的名义污染或混淆。逻辑上应该将foo效果与bar效果结合起来吗?一个需要另一个吗?当http $发出时,我是否可能不想触发foo?我是否在无关功能之间创建了不必要的耦合?这些都是避免将它们放在一起的原因。

所有这些甚至都没有考虑到错误处理,因为使用多个订阅更容易管理


谢谢您的回答。对不起,我只能接受一个答案。您的答案与@Matt Saunders所写的答案相同。这只是另一种观点。由于Matt的努力,我让他接受了。希望您能原谅我:)
乔纳森·斯泰尔瓦格
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.