我是否需要在 ngOnDestroy 中使用 `complete()` takeUntil Subject? [英] Do I need to `complete()` takeUntil Subject inside ngOnDestroy?

查看:19
本文介绍了我是否需要在 ngOnDestroy 中使用 `complete()` takeUntil Subject?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

为了避免组件内部的 Observable 内存泄漏,我在订阅 Observable 之前使用了 takeUntil() 运算符.

To avoid Observable memory leaks inside Components, I am using takeUntil() operator before subscribing to Observable.

我在我的组件中写了这样的东西:

I write something like this inside my components:

private unsubscribe$ = new Subject();

ngOnInit(): void {
  this.http
    .get('test')
    .pipe(takeUntil(this.unsubscribe$))
    .subscribe((x) => console.log(x));
}

ngOnDestroy(): void {
  this.unsubscribe$.next();
  this.unsubscribe$.complete(); // <--- ??
}

最后我的问题如下:

我需要写this.unsubscribe$.complete();还是next()就够了吗?

Do I need to write this.unsubscribe$.complete(); or next() is enough?

unsubscribe$ 会不会没完成就被垃圾收集器抓取?

Is unsubscribe$ going to be grabbed by garbage collector without completing?

请解释是否存在差异或无关紧要.我不希望我的组件出现内存泄漏.

Please explain is there difference or doesn't matter. I don't want memory leaks in my components.

推荐答案

简短的回答,不需要,但也无妨.

Short answer, no this is not needed, but it also doesn't hurt.

长答案:

只有在阻止垃圾收集时才需要取消订阅/完成 angular ,因为订阅涉及某些主题,该主题将因收集而比组件寿命更长.这就是内存泄漏的产生方式.

Unsubscribing / completing in angular is only needed when it prevents garbage collection because the subscription involves some subject that will outlive the component due to be collected. This is how a memory leak is created.

如果我有服务:

export class MyService {
  mySubject = new Subject();
}

它在 root 中提供并且仅在 root 中提供(这意味着它是一个单例并且在实例化后永远不会被销毁)以及一个注入此服务并订阅它的主题的组件

which is provided in root and only root (which means it's a singleton and will never be destroyed after instantiation) and a component that injects this service and subscribes to it's subject

export class MyLeakyComponent {
  constructor(private myService: MyService) {
    this.myService.mySubject.subscribe(v => console.log(v));
  }
}

这是造成内存泄漏.为什么?因为 MyLeakyComponent 中的订阅是由 MyService 中的主题引用的,所以只要 MyService 存在并持有对它的引用,MyLeakyComponent 就不能被垃圾收集,并且 MyService 将在应用程序的生命周期中存在.每次实例化 MyLeakyComponent 时,这都会复合.要解决此问题,您必须取消订阅或在组件中添加终止运算符.

this is creating a memory leak. Why? because the subscription in MyLeakyComponent is referenced by the subject in MyService, so MyLeakyComponent can't be garbage collected so long as MyService exists and holds a reference to it, and MyService will exist for the life of the application. This compounds everytime you instantiate MyLeakyComponent. To fix this, you must either unsubscribe or add a terminating operator in the component.

但是这个组件:

export class MySafeComponent {
  private mySubect = new Subject();
  constructor() {
    this.mySubject.subscribe(v => console.log(v));
  }
}

是完全安全的,可以毫无问题地进行垃圾回收.没有外部持久化实体持有对它的引用.这也是安全的:

is completely safe and will be garbage collected without issue. No external persisting entity holds a reference to it. This is also safe:

@Component({
  providers: [MyService]
})
export class MyNotLeakyComponent {
  constructor(private myService: MyService) {
    this.myService.mySubject.subscribe(v => console.log(v));
  }
}

现在注入的服务是由组件提供的,所以服务和组件会一起销毁,可以安全地垃圾回收,因为外部引用也会被销毁.

now the inejected service is provided by the component, so the service and the component will be destroyed together and can be safely garbage collected as the external reference will be destroyed as well.

这也是安全的:

export class MyHttpService { // root provided
  constructor(private http: HttpClient) {}

  makeHttpCall() {
    return this.http.get('google.com');
  }
}

export class MyHttpComponent {
  constructor(private myhttpService: MyHttpService) {
    this.myhttpService.makeHttpCall().subscribe(v => console.log(v));
  }
}

因为http调用是一类自终止的observable,所以调用完成后会自然终止,所以不需要手动完成或取消订阅,因为外部引用一旦自然完成就消失了.

because http calls are a class of observables that are self terminating, so they terminate naturally after the call completes, so no need to manually complete or unsubscribe, as the external reference is gone once it naturally completes.

至于你的例子:unsubscribe$ 主题是组件本地的,因此它不可能导致内存泄漏.任何地方主题都是如此.

As to your example: the unsubscribe$ subject is local to the component, thus it cannot possibly cause a memory leak. This is true of any local subject.

关于最佳实践的说明:Observables 是复杂的.一个看起来完全安全的,可能以一种微妙的方式涉及一个外部主题.为了完全安全/如果您对 observables 不是很满意,通常建议您取消订阅所有非终止 observables.除了你自己花时间做这件事外,没有其他缺点.我个人认为 unsubscribe$ 信号方法很糟糕,并认为它会污染/混淆您的流.对我来说最简单的是这样的:

A note on best practices: Observables are COMPLEX. One that might look completely safe, could involve an external subject in a subtle manner. To be totally safe / if you're not extremely comfortable with observables, it is generally recommended that you unsubscribe from all non terminating observables. There isn't a downside other than your own time spent doing it. I personally find the unsubscribe$ signal method hacky and think it pollutes / confuses your streams. the easiest to me is something like this:

export class MyCleanedComponent implements OnDestroy {
  private subs: Subscription[] = [];
  constructor(private myService: MyService) {
    this.subs.push(
      this.myService.mySubject.subscribe(v => console.log(v)),
      this.myService.mySubject1.subscribe(v => console.log(v)),
      this.myService.mySubject2.subscribe(v => console.log(v))
    );
  }

  ngOnDestroy() {
    this.subs.forEach(s => s.unsubscribe());
  }
}

然而,防止泄漏的最佳方法是尽可能使用 angular 提供的异步管道.它为您处理所有订阅管理.

However, the single BEST method for preventing leaks is using the async pipe provided by angular as much as possible. It handles all subscription management for you.

这篇关于我是否需要在 ngOnDestroy 中使用 `complete()` takeUntil Subject?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆