简而言之
对于这个问题,有两种Observables - 有限值和无限值。
http
Observables生成有限值(1),而像DOM事件监听器Observable生成无限值。
如果手动调用subscribe
(不使用异步管道),则需要从无限Observables中unsubscribe
。
不必担心有限的Observables,RxJs会处理它们。
来源:
我从Angular的Gitter
这里 找到了Rob Wormald的答案。他说(我为了更清晰地表达重新组织了一下,重点是我的):
如果它是一个单值序列(比如一个http请求),则手动清理不必要(假设你在控制器中手动订阅)。
我应该说“如果它是一个完成的序列”(其中单值序列,如http,是其中之一)。
如果它是一个无限序列,则应该取消订阅,异步管道会为您完成。
此外,在关于Observables的
YouTube视频中,他提到
“它们自己清理...”,在Observables的上下文中,这些Observables
完成(像Promises一样,因为它们总是产生一个值并结束——我们从来没有担心过取消订阅Promises以确保它们清理XHR事件侦听器,对吧?)
另外,在
Rangle Angular 2指南中还有这样一段话:
在大多数情况下,除非我们想要提前取消或我们的Observable的生命周期长于我们的订阅,否则我们不需要显式调用
unsubscribe
方法。Observable运算符的默认行为是在发布
.complete()
或
.error()
消息时处理订阅。请记住,RxJS大多数时间都是以“点火并忘记”的方式使用的。
什么时候适用于短语
“我们的Observable的生命周期长于我们的订阅”?
当在一个组件内创建一个订阅,并且该组件在Observable完成之前被销毁(或者不是“很久”之前)时,它就适用。
我理解为,如果我们订阅了一个
http
请求或一个发出10个值的Observable,并且在该
http
请求返回或10个值被发出之前我们的组件被销毁,我们仍然是安全的!当请求返回或第十个值最终被发出时,Observable会完成并清理所有资源。
如果我们看一下
同一Rangle指南中的这个示例,我们可以看到对
route.params
的订阅确实需要一个
unsubscribe()
,因为我们不知道这些
params
何时会停止改变(发出新值)。
如果导航离开,组件可能会被销毁,在这种情况下,路由参数可能仍然在改变(它们从技术上讲可以一直改变,直到应用程序结束),并且在订阅中分配的资源仍然会被分配,因为没有
完成。
在NgEurope的
这个视频中,Rob Wormald还说您不需要从Router Observables取消订阅。他还在
2016年11月的这个视频中提到了
http
服务和
ActivatedRoute.params
。
Angular教程中的
路由章节现在声明如下:
路由器管理它提供的可观察对象并本地化订阅。当组件被销毁时,订阅会被清理,以防止内存泄漏,因此我们不需要从路由
params
Observable
取消订阅。
这里有一个关于Angular文档中的路由器Observables的
GitHub Issues上的讨论,Ward Bell在其中提到,对所有这些内容的澄清正在进行中。
我在NGConf上与Ward Bell讨论了这个问题(我甚至向他展示了这个答案,他说它是正确的),但他告诉我Angular文档团队有一个未发布的解决方案(尽管他们正在努力获得批准)。他还告诉我,我可以在即将推出的官方建议中更新我的SO答案。
我们应该始终使用的解决方案是,在所有具有类代码中对Observables进行.subscribe()调用的组件中添加一个私有的ngUnsubscribe = new Subject<void>();字段。
然后在我们的ngOnDestroy()方法中调用this.ngUnsubscribe.next(); this.ngUnsubscribe.complete();。
如
@metamaker已经指出的秘密酱汁是,在每个.subscribe()调用之前调用takeUntil(this.ngUnsubscribe),这将确保在销毁组件时清除所有订阅。
例如:
import { Component, OnDestroy, OnInit } from '@angular/core';
import { filter, startWith, takeUntil } from 'rxjs/operators';
import { Subject } from 'rxjs';
import { BookService } from '../books.service';
@Component({
selector: 'app-books',
templateUrl: './books.component.html'
})
export class BooksComponent implements OnDestroy, OnInit {
private ngUnsubscribe = new Subject<void>();
constructor(private booksService: BookService) { }
ngOnInit() {
this.booksService.getBooks()
.pipe(
startWith([]),
filter(books => books.length > 0),
takeUntil(this.ngUnsubscribe)
)
.subscribe(books => console.log(books));
this.booksService.getArchivedBooks()
.pipe(takeUntil(this.ngUnsubscribe))
.subscribe(archivedBooks => console.log(archivedBooks));
}
ngOnDestroy() {
this.ngUnsubscribe.next();
this.ngUnsubscribe.complete();
}
}
注意:需要将
takeUntil
操作符作为最后一个操作符添加,以防止在操作符链中出现中间Observable泄漏。
最近,在Angular探险的一集中,Ben Lesh和Ward Bell讨论了组件中如何/何时取消订阅的问题。讨论从大约1:05:30开始。
Ward提到"现在有一个可怕的takeUntil舞蹈,需要很多机器",Shai Reznik提到"Angular处理了一些订阅,比如http和路由"。
作为回应,Ben提到现在正在讨论允许Observables钩入Angular组件生命周期事件,Ward建议使用组件可以订阅的生命周期事件的Observable来知道何时完成作为组件内部状态维护的Observables。
话虽如此,我们现在主要需要解决方案,以下是一些其他资源。
一位 RxJs 核心团队成员 Nicholas Jamieson 推荐了 takeUntil()
模式,并提供了一个 TSLint 规则帮助执行该模式:https://ncjamieson.com/avoiding-takeuntil-leaks/
这是一个轻量级的 npm 包,它向外部暴露了一个 Observable 操作符,以组件实例 (this
) 作为参数,在 ngOnDestroy
周期自动取消订阅:https://github.com/NetanelBasal/ngx-take-until-destroy
另一种变体比上面更加人性化,如果你没有进行 AOT 构建,则更好使用(但我们现在都应该进行 AOT 构建):https://github.com/smnbbrv/ngx-rx-collector
这个自定义 directive *ngSubscribe
的作用类似于 async pipe,但它在你的模板中创建了一个嵌入式视图,因此您可以在整个模板中引用“未包装”的值:https://netbasal.com/diy-subscription-handling-directive-in-angular-c8f6e762697f
我在Nicholas的博客评论中提到,过度使用
takeUntil()
可能意味着您的组件正在尝试做太多事情,应考虑将现有组件分成
功能和
表示组件。然后,您可以将Feature组件的Observable
| async
到Presentational组件的
Input
中,这意味着任何地方都不需要订阅。关于这种方法,请
在这里阅读更多信息。
http-requests
的Subscription
可以被忽略,因为它们只调用一次onNext
,然后调用onComplete
。相反,Router
会重复地调用onNext
,并且可能永远不会调用onComplete
(不确定......)。对于从Event
获得的Observable
也是如此。所以我猜应该将它们取消订阅。 - Robert Psubscribe
的调用本身。 - seangwrighttypescript
中,将明确取消订阅作为一种“肌肉记忆”。即使是http
订阅也要这样做。例如:如果你的Http.get()
在响应上完成。如果你的服务器 API 花费了10 秒
来响应,并且你的组件在调用后的5 秒
内被销毁,那么你的响应将在组件销毁之后的5 秒
到达。这将触发一个上下文不正确的执行,这比 Angular 文档中指出的内存泄漏部分更糟糕。 - Avid Coder