多函数订阅取消咨询:是否可复用同一Subscription变量?
嘿,这个问题我太熟了!咱们先把核心结论摆出来:你现在只靠this.subscription.unsubscribe()是没法取消所有订阅的,得额外做些处理,原因和靠谱的解决方法我给你掰扯明白~
为什么现有方式不行?
每个Observable.subscribe()调用都会返回一个独立的Subscription对象——你目前只把ngOnInit里getHeroes()的订阅赋值给了this.subscription,但onSelectVillain()里的订阅如果没关联到这个变量,就完全是另一个独立的订阅实例。调用this.subscription.unsubscribe()只会取消英雄列表的订阅,反派的订阅会一直留在内存里,时间长了就会导致内存泄漏。
举个实际的例子,如果你的onSelectVillain()是这么写的:
onSelectVillain(event) { this.villainService.getVillain(event.id) .subscribe( villain => this.selectedVillain = villain, error => this.errorMessage = <any>error ); }
这个订阅的Subscription对象根本没被保存,完全不受你现有this.subscription的控制。
两种靠谱的解决方法
方法一:用Subscription.add()合并多个订阅
你可以把所有订阅都添加到同一个Subscription实例里,这样一次unsubscribe()就能取消所有关联的订阅。修改代码如下:
import { Subscription } from 'rxjs'; // 声明一个主Subscription变量,专门用来合并所有订阅 private mainSubscription = new Subscription(); ngOnInit() { // 将getHeroes的订阅添加到主Subscription const heroesSub = this.heroService.getHeroes() .subscribe( heroes => this.heroes = heroes, error => this.errorMessage = <any>error ); this.mainSubscription.add(heroesSub); } onSelectVillain(event) { // 每次选择反派时,把新的订阅也添加到主Subscription const villainSub = this.villainService.getVillain(event.id) .subscribe( villain => this.selectedVillain = villain, error => this.errorMessage = <any>error ); this.mainSubscription.add(villainSub); } ngOnDestroy() { // 组件销毁时,一次取消所有合并的订阅 this.mainSubscription.unsubscribe(); }
方法二:用takeUntil操作符(Angular官方推荐)
这是更优雅的方式,不需要手动管理多个Subscription对象,适合多订阅场景:
import { Subject } from 'rxjs'; import { takeUntil } from 'rxjs/operators'; // 声明一个Subject,用来发送组件销毁的信号 private destroy$ = new Subject<void>(); ngOnInit() { this.heroService.getHeroes() .pipe(takeUntil(this.destroy$)) // 给订阅加上销毁信号监听 .subscribe( heroes => this.heroes = heroes, error => this.errorMessage = <any>error ); } onSelectVillain(event) { this.villainService.getVillain(event.id) .pipe(takeUntil(this.destroy$)) // 每个订阅都加上这个管道 .subscribe( villain => this.selectedVillain = villain, error => this.errorMessage = <any>error ); } ngOnDestroy() { // 发送销毁信号,所有带takeUntil的订阅都会自动取消 this.destroy$.next(); this.destroy$.complete(); }
这种方式的好处是,不管你后续加多少个订阅,只要在pipe里加上takeUntil(this.destroy$),组件销毁时都会自动取消,代码更整洁,也不容易遗漏。
额外提醒:避免重复订阅
如果onSelectVillain()会被用户多次触发(比如反复选择不同反派),每次触发都会创建新的订阅,之前的订阅还会存在。这时候可以用switchMap操作符自动取消之前的订阅:
// 先声明一个Subject来接收选择事件 private villainSelected$ = new Subject<number>(); ngOnInit() { // 用switchMap处理,每次新事件触发时自动取消之前的订阅 this.villainSelected$ .pipe( takeUntil(this.destroy$), switchMap(villainId => this.villainService.getVillain(villainId)) ) .subscribe( villain => this.selectedVillain = villain, error => this.errorMessage = <any>error ); } onSelectVillain(event) { // 只需要发送事件,不用每次都手动调用subscribe this.villainSelected$.next(event.id); }
内容的提问来源于stack exchange,提问作者user8743396

