关于前端:unsubscribeAngular-项目中常见场景以及是否需要-unsubscribe

45次阅读

共计 7711 个字符,预计需要花费 20 分钟才能阅读完成。

本文由庄汇晔同学编写~

在 Angular 我的项目中,常常会应用到 observable subscribe,然而 subscribe 读取了数据之后,真的就是高枕无忧了吗?这个问题的答案或者是,或者不是。有些 observable 须要 unsubscribe,而有些并不必。在接下来的文章里,我会介绍:

  • observable 的品种:何种 observable 须要 unsubscribe,以及没有 unsubscribe 会造成什么样的问题。
  • 在 angular 我的项目中,可能会遇到的 observable 的场景,以及他们是否须要 unsubscribe,为什么须要 / 不须要?
  • unsubscribe 的办法。

一、observable 的品种:何种 observable 须要 unsubscribe, 以及没有 unsubscribe 会造成什么样的问题。

从 observable 是否完结(complete)的角度来看,observable 分为两类:finite observable(无限事件流)和 infinite observable(有限事件流)。辨别他们的办法很简略:即 finite observable 只能 emit 一个值(或是一个谬误),随即完结。而 infinite observable 会 emit 多个值,而不会完结。

finite Observable 的例子有:http client request,infinite Observable 的例子有:DOM eventListener。如果不勾销订阅,将会呈现内存透露,执行意料之外回调函数的问题。所以,肯定须要 unsubscribe 的是 infinite observable(有限事件流),而 finite observable(无限事件流)个别不须要 unsubscribe。

二、在 angular 我的项目中, 可能会遇到的 subscribe 的场景,他们须要 unsubscribe 吗?为什么须要 / 不须要?

1、Http Client 申请(this.httpClient.get(…).subscribe)

fetchFromBackend() {let subscription$ = this.http.get(`http://example.com`).subscribe(...)
}

是否须要 unsubscribe:视状况而定。

起因:http client 为无限事件流,当 Observable complete 后,angular 会主动敞开 Observable。因为 unsubscribe http client request 并不意味着勾销申请,而是勾销了返回数据后的回调函数,所以须要依据场合来勾销 subscription。如果在回调函数中,如果有会造成内存透露的代码,则严格意义上来说,应该须要 unsubscribe

更多详情请移步浏览:https://medium.com/angular-in-depth/why-you-have-to-unsubscri…

2、Component 之间传递信息应用的 Behavior subject

// in service file
@Injectable({providedIn: 'any',})
export class MyComponentService {public dataSource = new BehaviorSubject < any > (null);
  ....
}

// in component data boardcaster
this.myComponentService.dataSource.next(...)

// in component data subscriber
this.myComponentService.dataSource.subscribe(...)

是否须要 unsubscribe:须要。

起因:这种为有限事件流,无奈预期它的回调函数会造成什么样的问题,所以须要 unsubscribe

3、Form control,响应式表单的变动监听(例如:this.form.valueChanges.subscribe)

initForm() {this.form = new FormGroup({ ...});
  this.valueChanges = this.form.valueChanges.subscribe(...);
}

是否须要 unsubscribe:须要。

起因:这种为有限事件流,尽管 component unmount 后,form 也不存在,然而这样会造成内存透露,导致性能降落等问题。

4、Dom 中的 fromEvent 事件监听(例如:Observable.fromEvent(this.element.nativeElement, ‘click’).subscribe)

@ViewChild('myElement', { static: false})
myElement: ElementRef;
constructor(private element : ElementRef) { }

click: Subscription;

ngOnInit() {this.click = Observable.fromEvent(this.myElement.nativeElement, 'click').subscribe(...);
}

是否须要 unsubscribe:须要。

起因:这种为有限事件流,尽管 component unmount 后,dom element 不存在,然而这样会造成内存透露,导致性能降落等问题。

5、router 变动事件(例如:this.route.queryParams.subscribe)

constructor(private route: ActivatedRoute, private router: Router) {this.route.params.subscribe(console.log);
  this.route.queryParams.subscribe(console.log);
  this.route.fragment.subscribe(console.log);
  this.route.data.subscribe(console.log);
  this.route.url.subscribe(console.log);
  
  this.router.events.subscribe(console.log);

}

是否须要 unsubscribe:须要。

起因:这种为有限事件流,如果不 unsubscribe,会运行谬误的回调函数,造成不可控的问题

三、unsubscribe 的办法

1,应用 rxjs takeUntil,takeWhile,take(n),first operator:

简介:rxjs take 系列的 operator 能够限定获得 Observable emit 的次数或者机会,当次数或机会不合乎时,Observable 就会走向实现状态。所以,在 angular 我的项目中,能够灵便利用组件的生命周期函数,使得组件须要 unsubscribe 的 Observable 在组件销毁时,主动实现。first operator 则和 take(1) 相似,是只获得第一个 emit 值,随后实现。

takeUntil: https://rxjs.dev/api/index/function/takeUntil

takeUntil(notifier: ObservableInput) 会始终监听他的数据源,直到 notifier Observable emit 一个值

import {Subject, takeUntil} from 'rxjs';

export class Foo implements OnInit {private destroy$ = new Subject();
  constructor() {}

  ngOnInit(): void {
    this.auth.fireAuthUser$
      .pipe(takeUntil(this.destroy$))
      .subscribe({next: (data) => console.log(data),
        complete: () => this.uponComplete(),
      });
  }

  uponComplete() {console.log('Finally completed');
  }

  ngOnDestroy() {this.destroy$.next(true);
    this.destroy$.complete();}
}

takeWhile: https://rxjs.dev/api/index/function/takeWhile

与 takeUnitl 相似,只是 takeUnitl 的参数是一个判断函数,而 takeWhile 的参数是一个 Observable export class ViewRouteComponent implements OnInit, OnDestroy {alive: boolean = true

constructor(private titleService: TitleService) {ngOnInit() {
    this.titleService.emitter1$
      .pipe(takeWhile(() => this.alive))
      .subscribe((data: any) => {/_ ... do something 1 _/})
  }

  ngOnDestroy() {this.alive = false}
}

take(n): https://rxjs.dev/api/index/function/take

在获得 n 次 emit 之后,Observable 会主动实现

@Component({...})
export class AppComponent implements OnInit {
  subscription$
  ngOnInit() {let observable$ = Rx.Observable.interval(1000);
    this.subscription$ = observable$.pipe(take(1)).
      subscribe(x => console.log(x))
  }
}

first: https://rxjs.dev/api/index/function/first

在获得第一次 emit 后,Observable 主动实现

@Component({...})
export class AppComponent implements OnInit {
  subscription$
  ngOnInit() {let observable$ = Rx.Observable.interval(1000);
    this.subscription$ = observable$.pipe(first()).
      subscribe(x => console.log(x))
  }
}

2,应用 unsubscribe 办法

应用 unsubscribe 办法是十分直观的一种形式,只需在 angular 组件的 ngOnDesrtoy 中调用即可。

这里的例子应用了一个 subscription array,在 ngOnDestroy 中 unsubscribe

export class AppComponent implements OnInit, OnDestroy {
  subscription1$: Subscription
  subscription2$: Subscription
  subscriptions: Subscription[] = []
  ngOnInit() {var observable1$ = Rx.Observable.interval(1000);
    var observable2$ = Rx.Observable.interval(400);
    this.subscription1$ = observable1$.subscribe(x =>
      console.log("From interval 1000", x)
    );
    this.subscription2$ = observable2$.subscribe(x =>
      console.log("From interval 400", x)
    );
    this.subscriptions.push(this.subscription1$);
    this.subscriptions.push(this.subscription2$);
  }
  ngOnDestroy() {this.subscriptions.forEach((subscription) =>
      subscription.unsubscribe());
  }
}

3,应用 async pipe

官网文档:https://angular.io/api/common/AsyncPipe

应用这种形式能够无需手动 unsubscribe,也不必放心内存透露的问题。然而它的局限性也在于,它只能被应用在模版(template)中。请思考如下场景:在用户点击按钮后,须要提交一个表单。此时,应用 async pipe 则变得不怎么适合了。

@Component({
  selector: 'async-observable-pipe',
  template: '<div><code>observable|async</code>: Time: {{time | async}}</div>'
})
export class AsyncObservablePipeComponent {time = new Observable < string > ((observer: Observer<string>) => {setInterval(() => observer.next(new Date().toString()), 1000);
  });
}

补充:如何查看组件销毁后,是否存在 unsubscribe 的 subscription?

办法 1:从动态代码查看中躲避

应用 eslint 工具,配置 rxjs-no-ignored-subscription 规定:https://github.com/cartant/eslint-plugin-rxjs/blob/main/docs/rules/no-ignored-subscription.md

办法 2:应用堆快照查看内存透露

例子中想要查看内存透露的组件是 MonthlyReportComponent,此时应用开发者工具,在加载组件前应用快照性能,并搜寻组件名称,是看不到内存透露的

在组件加载后,unmount,再次捕获快照,再次搜寻组件名称,查看堆快照:

查看外面是否有 BehaviorSubject,Observable 等未勾销订阅的对象

参考文档:

http client:

https://stackoverflow.com/questions/35042929/is-it-necessary-to-unsubscribe-from-observables-created-by-http-methods 

https://stackoverflow.com/questions/38008334/angular-rxjs-when-should-i-unsubscribe-from-subscription 

https://medium.com/angular-in-depth/why-you-have-to-unsubscri…

如何 unsubscribe:

https://stackoverflow.com/questions/41364078/angular-2-does-subscribing-to-formcontrols-valuechanges-need-an-unsubscribe 

https://blog.bitsrc.io/6-ways-to-unsubscribe-from-observables… 

https://benlesh.medium.com/rxjs-dont-unsubscribe-6753ed4fda87 

https://stackoverflow.com/questions/69333761/angular-12-rxjs-safe-to-use-takewhile-without-ondestroy 

https://stackoverflow.com/questions/38008334/angular-rxjs-when-should-i-unsubscribe-from-subscription

如何查看存在 unsubscribe:

https://itnext.io/angular-rxjs-detecting-memory-leaks-bdd312a…

对于 OpenTiny

OpenTiny 是一套企业级 Web 前端开发解决方案,提供跨端、跨框架、跨版本的 TinyVue 组件库,蕴含基于 Angular+TypeScript 的 TinyNG 组件库,领有灵便扩大的低代码引擎 TinyEngine,具备主题配置零碎 TinyTheme / 中后盾模板 TinyPro/ TinyCLI 命令行等丰盛的效率晋升工具,可帮忙开发者高效开发 Web 利用。


欢送退出 OpenTiny 开源社区。增加微信小助手:opentiny-official 一起参加交换前端技术~更多视频内容也可关注 B 站、抖音、小红书、视频号
OpenTiny 也在继续招募贡献者,欢送一起共建

OpenTiny 官网:https://opentiny.design/
OpenTiny 代码仓库:https://github.com/opentiny/
TinyVue 源码:https://github.com/opentiny/tiny-vue
TinyEngine 源码:https://github.com/opentiny/tiny-engine

欢送进入代码仓库 Star🌟TinyEngine、TinyVue、TinyNG、TinyCLI~
如果你也想要共建,能够进入代码仓库,找到 good first issue 标签,一起参加开源奉献~

正文完
 0