什么时候我应该存储订阅实例和调用unsubscribe()在ngOnDestroy生命周期,什么时候我可以简单地忽略它们?

保存所有订阅会给组件代码带来很多麻烦。

HTTP客户端指南忽略这样的订阅:

getHeroes() {
  this.heroService.getHeroes()
                  .subscribe(
                     heroes => this.heroes = heroes,
                     error =>  this.errorMessage = <any>error);
}

同时,《航路指南》指出:

最终,我们会航行到别的地方。路由器将从DOM中移除这个组件并销毁它。在那之前,我们得把自己弄干净。具体来说,我们必须在Angular销毁该组件之前取消订阅。如果不这样做,可能会产生内存泄漏。 我们在ngOnDestroy方法中取消订阅我们的可观察对象。

private sub: any;

ngOnInit() {
  this.sub = this.route.params.subscribe(params => {
     let id = +params['id']; // (+) converts string 'id' to a number
     this.service.getHero(id).then(hero => this.hero = hero);
   });
}

ngOnDestroy() {
  this.sub.unsubscribe();
}

当前回答

根据@seangwright的回答,我写了一个抽象类来处理组件中“无限”的可观察对象的订阅:

import { OnDestroy } from '@angular/core';
import { Subscription } from 'rxjs/Subscription';
import { Subject } from 'rxjs/Subject';
import { Observable } from 'rxjs/Observable';
import { PartialObserver } from 'rxjs/Observer';

export abstract class InfiniteSubscriberComponent implements OnDestroy {
  private onDestroySource: Subject<any> = new Subject();

  constructor() {}

  subscribe(observable: Observable<any>): Subscription;

  subscribe(
    observable: Observable<any>,
    observer: PartialObserver<any>
  ): Subscription;

  subscribe(
    observable: Observable<any>,
    next?: (value: any) => void,
    error?: (error: any) => void,
    complete?: () => void
  ): Subscription;

  subscribe(observable: Observable<any>, ...subscribeArgs): Subscription {
    return observable
      .takeUntil(this.onDestroySource)
      .subscribe(...subscribeArgs);
  }

  ngOnDestroy() {
    this.onDestroySource.next();
    this.onDestroySource.complete();
  }
}

要使用它,只需在你的angular组件中扩展它,并调用subscribe()方法,如下所示:

this.subscribe(someObservable, data => doSomething());

它还像往常一样接受错误和完整的回调,接受一个观察者对象,或者根本不接受回调。如果你在子组件中也实现了这个方法,记得调用super.ngOnDestroy()。

在这里可以找到Ben Lesh的另一篇参考文章:RxJS: Don’t Unsubscribe。

其他回答

视情况而定。如果通过调用someObservable.subscribe(),您开始占用一些资源,当组件的生命周期结束时必须手动释放这些资源,那么您应该调用subscription .unsubscribe()来防止内存泄漏。

让我们仔细看看你的例子:

getHero()返回http.get()的结果。如果你查看angular 2的源代码,会发现http.get()创建了两个事件监听器:

_xhr.addEventListener('load', onLoad);
_xhr.addEventListener('error', onError);

通过调用unsubscribe(),你可以取消请求和监听器:

_xhr.removeEventListener('load', onLoad);
_xhr.removeEventListener('error', onError);
_xhr.abort();

请注意,_xhr是特定于平台的,但我认为在您的情况下,可以安全地假设它是XMLHttpRequest()。

通常,这已经足够证明需要手动unsubscribe()调用了。但是根据WHATWG规范,XMLHttpRequest()一旦“完成”就会受到垃圾收集的影响,即使有事件监听器附加到它。所以我想这就是为什么angular 2官方指南省略了unsubscribe(),让GC清理侦听器。

至于第二个例子,它取决于参数的实现。从今天起,angular官方指南不再显示取消订阅参数。我再次查看了src,发现params只是一个行为主体。由于没有使用事件侦听器或计时器,也没有创建全局变量,因此省略unsubscribe()应该是安全的。

你的问题的底线是总是调用unsubscribe()来防止内存泄漏,除非你确定可观察对象的执行不会创建全局变量、添加事件侦听器、设置计时器或做任何其他导致内存泄漏的事情。

如果有疑问,请查看该可观察对象的实现。如果可观察对象已经在其unsubscribe()中写入了一些清理逻辑,这通常是构造函数返回的函数,那么你有充分的理由认真考虑调用unsubscribe()。

由于seangwright的解决方案(编辑3)似乎非常有用,我也发现将这个功能打包到基本组件中是一个痛苦的过程,并提示其他项目队友记住在ngOnDestroy上调用super()来激活这个功能。

这个答案提供了一种从super调用中释放的方法,并使"componentDestroyed$"成为base component的核心。

class BaseClass {
    protected componentDestroyed$: Subject<void> = new Subject<void>();
    constructor() {

        /// wrap the ngOnDestroy to be an Observable. and set free from calling super() on ngOnDestroy.
        let _$ = this.ngOnDestroy;
        this.ngOnDestroy = () => {
            this.componentDestroyed$.next();
            this.componentDestroyed$.complete();
            _$();
        }
    }

    /// placeholder of ngOnDestroy. no need to do super() call of extended class.
    ngOnDestroy() {}
}

然后你可以自由地使用这个功能,例如:

@Component({
    selector: 'my-thing',
    templateUrl: './my-thing.component.html'
})
export class MyThingComponent extends BaseClass implements OnInit, OnDestroy {
    constructor(
        private myThingService: MyThingService,
    ) { super(); }

    ngOnInit() {
        this.myThingService.getThings()
            .takeUntil(this.componentDestroyed$)
            .subscribe(things => console.log(things));
    }

    /// optional. not a requirement to implement OnDestroy
    ngOnDestroy() {
        console.log('everything works as intended with or without super call');
    }

}

上述情况的另一个简短补充是:

总是取消订阅,当订阅流中的新值不再需要或无关紧要时,它将导致更少的触发器数量,并在某些情况下提高性能。订阅的数据/事件不再存在,或者需要对全新流进行新订阅(刷新等)的组件就是取消订阅的好例子。

Angular 2官方文档提供了一个关于何时退订以及何时可以安全忽略的解释。看看这个链接:

https://angular.io/docs/ts/latest/cookbook/component-communication.html !# bidirectional-service

寻找标题为“父母和孩子通过服务通信”的段落,然后是蓝色方框:

注意,当AstronautComponent被销毁时,我们捕获了订阅并取消了订阅。这是一个内存泄漏保护步骤。在这个应用程序中没有实际的风险,因为AstronautComponent的生命周期与应用程序本身的生命周期相同。在更复杂的应用程序中,这并不总是正确的。 我们没有将这个守卫添加到MissionControlComponent中,因为作为父组件,它控制着MissionService的生命周期。

我希望这对你有所帮助。

基于:使用类继承来钩子到Angular 2的组件生命周期

另一种通用方法:

导出抽象类UnsubscribeOnDestroy实现OnDestroy { protected d$: Subject<any>; 构造函数(){ 这一点。d$ = new Subject<void>(); const f = this.ngOnDestroy; 这一点。ngOnDestroy = () => { f (); this.d $ . next (); this.d .complete美元(); }; } public ngOnDestroy() { / /空操作 } }

并使用:

@ component ({ 选择器:“my-comp”, 模板:“ }) 导出类RsvpFormSaveComponent扩展UnsubscribeOnDestroy实现OnInit { 构造函数(){ 超级(); } ngOnInit(): void { Observable.of (bla) .takeUntil (this.d $) .subscribe(val => console.log(val)); } }