我是否需要在 ngOnDestroy 中`complete()` takeUntil Subject?
Do I need to `complete()` takeUntil Subject inside ngOnDestroy?
为了避免组件内部的 Observable 内存泄漏,我在订阅 Observable 之前使用了 takeUntil()
运算符。
我在我的组件中写了这样的东西:
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()
就够了?
是不是unsubscribe$
没有完成就被垃圾回收器抓取了?
请说明有区别还是无所谓。我不希望我的组件出现内存泄漏。
您只需要unsubscribe
赞:
subscr: Subscription;
ngOnInit() {
this.subscr = this.http
.get('test')
.pipe(takeUntil(this.unsubscribe$))
.subscribe(x => console.log(x));
}
ngOnDestroy() {
this.subscr.unsubscribe();
}
到 unsubscribe
使用 takeUntil()
:
解决方案是使用 takeUntil
运算符组合我们的订阅,并使用在 ngOnDestroy
:
中发出 truthy
值的主题
export class AppComponent implements OnInit, OnDestroy {
destroy$: Subject<boolean> = new Subject<boolean>();
ngOnInit() {
this.http
.get('test')
.pipe(takeUntil(this.unsubscribe$))
.subscribe(x => console.log(x));
}
ngOnDestroy() {
this.destroy$.next(true);
}
}
请注意,使用像 takeUntil
这样的运算符而不是手动取消订阅也将完成可观察对象,触发可观察对象的任何完成事件。
见here
我看到您正在订阅 http 响应。使用 Angular HttpClient 时有一件重要的事情:您不必取消订阅,因为它是自动完成的!
您可以使用 finalize 运算符对其进行测试 - 它会在 observable 完成时调用。 (当 observable 完成时,它会自动取消订阅)
this.http
.get('test')
.pipe(takeUntil(this.unsubscribe$), finalize(() => console.log('I completed and unsubscribed')))
.subscribe((x) => console.log(x));
如果您担心您的组件可能会在 HTTP 请求仍在进行时挂掉,并且回调中的代码可能仍会执行,您可以这样做:
private subscription: Subscription;
ngOnInit(): void {
this.subscription = this.http
.get('test')
.subscribe((x) => console.log(x));
}
ngOnDestroy(): void {
// Here you should also do a check if subscription exists because in most cases it won't
this.subscription.unsubscribe();
}
同样值得检查 AsyncPipe
The AsyncPipe subscribes (and unsubscribes) for you automatically.
简短的回答,不,这不是必需的,但也没有坏处。
长答案:
只有在阻止垃圾收集时才需要在 angular 中取消订阅/完成,因为订阅涉及一些主题,这些主题将比要收集的组件更长寿。这就是内存泄漏的产生方式。
如果我有服务:
export class MyService {
mySubject = new Subject();
}
它在根中提供并且只有根(这意味着它是一个单例并且在实例化后永远不会被销毁)和一个注入该服务并订阅它的组件
export class MyLeakyComponent {
constructor(private myService: MyService) {
this.myService.mySubject.subscribe(v => console.log(v));
}
}
这会造成内存泄漏。为什么?因为 MyLeakyComponent 中的订阅被 MyService 中的主题引用,所以只要 MyService 存在并持有对它的引用,MyLeakyComponent 就不能被垃圾回收,并且 MyService 将在应用程序的生命周期内存在。每次您实例化 MyLeakyComponent 时,这都会复合。要解决此问题,您必须取消订阅或在组件中添加终止运算符。
但是这个组件:
export class MySafeComponent {
private mySubect = new Subject();
constructor() {
this.mySubject.subscribe(v => console.log(v));
}
}
是完全安全的,将毫无问题地被垃圾回收。没有外部持久实体持有对它的引用。这也是安全的:
@Component({
providers: [MyService]
})
export class MyNotLeakyComponent {
constructor(private myService: MyService) {
this.myService.mySubject.subscribe(v => console.log(v));
}
}
现在注入的服务由组件提供,因此服务和组件将一起销毁,并且可以安全地进行垃圾回收,因为外部引用也将被销毁。
这也是安全的:
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 调用是自终止的 class 可观察对象,所以它们会在调用完成后自然终止,因此无需手动完成或取消订阅,因为外部引用一旦自然完成就会消失.
以你的例子为例:
unsubscribe$
主题是组件本地的,因此它不可能导致内存泄漏。任何本地主题都是如此。
关于最佳做法的说明:
Observable 是复杂的。一个看起来完全安全的人,可能会以一种微妙的方式涉及外部主体。为了完全安全/如果您对可观察对象不是很满意,通常建议您取消订阅所有非终止可观察对象。除了你自己花时间做这件事之外没有任何缺点。我个人认为 unsubscribe$ 信号方法很老套,并认为它会污染/混淆您的流。对我来说最简单的是这样的:
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 提供的异步管道。它为您处理所有订阅管理。
为了避免组件内部的 Observable 内存泄漏,我在订阅 Observable 之前使用了 takeUntil()
运算符。
我在我的组件中写了这样的东西:
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()
就够了?
是不是unsubscribe$
没有完成就被垃圾回收器抓取了?
请说明有区别还是无所谓。我不希望我的组件出现内存泄漏。
您只需要unsubscribe
赞:
subscr: Subscription;
ngOnInit() {
this.subscr = this.http
.get('test')
.pipe(takeUntil(this.unsubscribe$))
.subscribe(x => console.log(x));
}
ngOnDestroy() {
this.subscr.unsubscribe();
}
到 unsubscribe
使用 takeUntil()
:
解决方案是使用 takeUntil
运算符组合我们的订阅,并使用在 ngOnDestroy
:
truthy
值的主题
export class AppComponent implements OnInit, OnDestroy {
destroy$: Subject<boolean> = new Subject<boolean>();
ngOnInit() {
this.http
.get('test')
.pipe(takeUntil(this.unsubscribe$))
.subscribe(x => console.log(x));
}
ngOnDestroy() {
this.destroy$.next(true);
}
}
请注意,使用像 takeUntil
这样的运算符而不是手动取消订阅也将完成可观察对象,触发可观察对象的任何完成事件。
见here
我看到您正在订阅 http 响应。使用 Angular HttpClient 时有一件重要的事情:您不必取消订阅,因为它是自动完成的!
您可以使用 finalize 运算符对其进行测试 - 它会在 observable 完成时调用。 (当 observable 完成时,它会自动取消订阅)
this.http
.get('test')
.pipe(takeUntil(this.unsubscribe$), finalize(() => console.log('I completed and unsubscribed')))
.subscribe((x) => console.log(x));
如果您担心您的组件可能会在 HTTP 请求仍在进行时挂掉,并且回调中的代码可能仍会执行,您可以这样做:
private subscription: Subscription;
ngOnInit(): void {
this.subscription = this.http
.get('test')
.subscribe((x) => console.log(x));
}
ngOnDestroy(): void {
// Here you should also do a check if subscription exists because in most cases it won't
this.subscription.unsubscribe();
}
同样值得检查 AsyncPipe
The AsyncPipe subscribes (and unsubscribes) for you automatically.
简短的回答,不,这不是必需的,但也没有坏处。
长答案:
只有在阻止垃圾收集时才需要在 angular 中取消订阅/完成,因为订阅涉及一些主题,这些主题将比要收集的组件更长寿。这就是内存泄漏的产生方式。
如果我有服务:
export class MyService {
mySubject = new Subject();
}
它在根中提供并且只有根(这意味着它是一个单例并且在实例化后永远不会被销毁)和一个注入该服务并订阅它的组件
export class MyLeakyComponent {
constructor(private myService: MyService) {
this.myService.mySubject.subscribe(v => console.log(v));
}
}
这会造成内存泄漏。为什么?因为 MyLeakyComponent 中的订阅被 MyService 中的主题引用,所以只要 MyService 存在并持有对它的引用,MyLeakyComponent 就不能被垃圾回收,并且 MyService 将在应用程序的生命周期内存在。每次您实例化 MyLeakyComponent 时,这都会复合。要解决此问题,您必须取消订阅或在组件中添加终止运算符。
但是这个组件:
export class MySafeComponent {
private mySubect = new Subject();
constructor() {
this.mySubject.subscribe(v => console.log(v));
}
}
是完全安全的,将毫无问题地被垃圾回收。没有外部持久实体持有对它的引用。这也是安全的:
@Component({
providers: [MyService]
})
export class MyNotLeakyComponent {
constructor(private myService: MyService) {
this.myService.mySubject.subscribe(v => console.log(v));
}
}
现在注入的服务由组件提供,因此服务和组件将一起销毁,并且可以安全地进行垃圾回收,因为外部引用也将被销毁。
这也是安全的:
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 调用是自终止的 class 可观察对象,所以它们会在调用完成后自然终止,因此无需手动完成或取消订阅,因为外部引用一旦自然完成就会消失.
以你的例子为例:
unsubscribe$
主题是组件本地的,因此它不可能导致内存泄漏。任何本地主题都是如此。
关于最佳做法的说明: Observable 是复杂的。一个看起来完全安全的人,可能会以一种微妙的方式涉及外部主体。为了完全安全/如果您对可观察对象不是很满意,通常建议您取消订阅所有非终止可观察对象。除了你自己花时间做这件事之外没有任何缺点。我个人认为 unsubscribe$ 信号方法很老套,并认为它会污染/混淆您的流。对我来说最简单的是这样的:
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 提供的异步管道。它为您处理所有订阅管理。