我应该什么时候为特定的副作用创建一个新的订阅?

Jon*_*wag 9 javascript reactive-programming rxjs typescript angular

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

设置信息

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

什么是副作用(Angular 示例)

  • UI 中的更新/输入(例如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);
Run Code Online (Sandbox Code Playgroud)

案例:一般最小化订阅

http$.pipe(
  tap(() => foo()),
  switchMap(httpValue => click$.pipe(
    mapTo(httpValue )
  )
).subscribe(bar);
Run Code Online (Sandbox Code Playgroud)

简而言之我自己的看法

我可以理解订阅一开始会使 Rx 环境变得更加复杂的事实,因为您必须考虑订阅者应该如何影响管道或不影响管道(例如是否共享您的 observable)。但是,您将代码分离得越多(您越关注:什么时候会发生什么),将来维护(测试、调试、更新)代码就越容易。考虑到这一点,我总是为我的代码中的任何副作用创建一个单一的可观察源和一个单一的订阅。如果我的两个或多个副作用是由完全相同的源 observable 触发的,那么我会共享我的 observable 并单独订阅每个副作用,因为它可以有不同的生命周期。

Mat*_*ers 6

RxJS 是管理异步操作的宝贵资源,应尽可能用于简化您的代码(包括减少订阅数量)。同样,如果 RxJS 提供了可以减少应用程序中订阅总数的解决方案,那么 observable 不应自动跟随对该 observable 的订阅。

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

一个示例异常 - 在单个模板中重用 observable

看你的第一个例子:

// Component:

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

// Template:

<div>{{value$ | async}}</div>
Run Code Online (Sandbox Code Playgroud)

如果 value$ 在模板中只使用一次,我会利用异步管道及其代码经济性和自动取消订阅的好处。但是,根据此答案,应避免在模板中多次引用同一个异步变量,例如:

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

<ul *ngIf="value$ | async">
    <li *ngFor="let val of value$ | async">{{val}}</li>
</ul>
Run Code Online (Sandbox Code Playgroud)

在这种情况下,我会创建一个单独的订阅并使用它来更新组件中的非异步变量:

// 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>
Run Code Online (Sandbox Code Playgroud)

从技术上讲,没有 也可以达到相同的结果valueSub,但应用程序的要求意味着这是正确的选择。

在决定是否订阅之前考虑 observable 的角色和生命周期

如果两个或多个 observable 仅在组合在一起时才有用,则应使用适当的 RxJS 运算符将它们组合成一个订阅。

类似地,如果first()被用来过滤掉除了第一次发出的 observable 之外的所有内容,我认为比起在会议。

如果任何单独的可观察量独立于其他可观察量是有用的,则单独订阅的灵活性和清晰度可能值得考虑。但根据我最初的声明,不应为每个 observable 自动创建订阅,除非有明确的理由这样做。

关于退订:

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

个人偏好/冗长与简洁:

请务必考虑您自己的喜好。我不想偏离关于代码冗长的一般性讨论,但目标应该是在过多的“噪音”和使您的代码过于神秘之间找到正确的平衡。这可能值得一看

  • 我对取消订阅的偏好是“takeUntil”与“ngOnDestroy”调用的函数结合使用。这是一个单行代码,将其添加到管道中:`takeUntil(componentDestroyed(this))`。/sf/answers/4215662461/ (2认同)