下载APP

rxjs取消订阅的几种方式

使用rxjs会经常用到取消订阅的操作,下面讲讲其取消订阅的几种方式

订阅返回对象

创建一个可观察对象,如:const observable = new Observable((subscriber) => {subscriber.next(1)}), 通过observable对象订阅返回可以订阅对象,每一次订阅返回一个对应的可订阅对象,所以可以通过可订阅对象取消对应订阅,如下

let subscriber
const observable = new Observable((subscriber) => {
    subscriber.next(1); 
    subscriber = subscriber
})
// 订阅生成可订阅对象
const subscription$ = observable.subscribe(function subscribe(data) { console.log(data) })

// 取消订阅, subscribe函数不再执行,只会打印1,后面取消订阅了,2将不会打印
subscription$.unsubscribe()
subscriber.next(2) 

这种方式当订阅对象多了,就很烦了,有多少个就要保存多少个变量,再一个个取消,但是肯定有存在的必要,当取消条件不同时比较合适,因条件不同所以必须保证订阅对象有区分,所以要用这样的方式

条件取消

条件取消使用takeUntil操作符,使用方法如下:

import { interval, fromEvent, takeUntil } from 'rxjs';

const source = interval(1000);
// 点击就会有流推出来
const clicks = fromEvent(document, 'click');
// takeUntil(clicks) 的意思是:clicks只要推出一个流,那么result订阅就会自动取消
const result = source.pipe(takeUntil(clicks)).subscribe(x => console.log(x));

这个常用套路就声明一个Subject对象,如下

const destroy$ = new Subject()

// 上面clicks换成 destroy$
// 再自己手动在要取消的时候调用 destroy$.next()
destroy$.next()
destroy$.complete() // 销掉Subject

看到这里感觉和上面的方式没什么区别,代码也没少什么,但这里可以集中取消,比如订阅了很多个,取消条件一样,那么最后取消时,只用在takeUntil中的对象推一次流就全部取消了,可以减少变量声明和成对的重复取消模板

多订阅场景,很多用的这种方式,也是比较推荐的方式,最常用的场景是在页面销毁时,执行上面最后两行代码,取消所有订阅

直接声明

直接声明Subscription类管理,直接上代码

import { interval, Subscription } from 'rxjs';
const subscription$ =  new Subscription()
const source = interval(1000);
// 想要一起取消订阅对象可以加入里面
subscription$.add(source.subscribe(() => {console.log(1)}))
subscription$.add(source.subscribe(() => {console.log(2)}))
subscription$.add(source.subscribe(() => {console.log(3)}))
// 取消订阅,这里会取消上面的三个订阅
subscription$.unsubscribe()

这个和条件取消使用场景差不多,目前用的很少,没有takeUntil好用

好不好用,实践才知道

大多数场景是离开当前页面时取消相关订阅,所以多页面应用不用考虑(刷新页面没有了),单页应用spa,当离开当前页面时使用取消订阅,这里以vue和angular使用为例

vue3示例:

const source = interval(1000);
const sub$ = source.subscribe(() => {console.log(1)})
onUnmounted(() => {
   sub$.unsubscribe()
})

改用use实现

function useSub(sub) {
    onUnmounted(() => {
       sub.unsubscribe()
    }) 
}

// 那么就可以这样调用
useSub(source.subscribe(() => {console.log(1)}))

和vue无关写法, 结合响应式作用域来实现

// 在不清楚是否活跃时,可以用返回值判断,是否生效 这个在vue组件里用肯定活跃的,所以不用判断反回值
function useSub(sub) {
   // 判断当前作用域是否活跃
   if (getCurrentScope()) {
      // 作用域不活跃时,取消订阅
       onScopeDispose(() => {
           sub.unsubscribe()
       }) 
       return true 
   } 
   return false
}

angular示例

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.less']
})
export class AppComponent implements OnDestroy  {
  destroy$ = new Subject()
  constructor() {
    fromEvent(document, 'click').pipe(takeUntil(this.destroy$)).subscribe(() => {})
  }
  ngOnDestroy(): void {
    this.destroy$.next()
    this.destroy$.complete()
  }
 }
}

service 封装

// 服务
@Injectable()
export class DestroyService extends Subject implements OnDestroy {
  constructor() { }
  ngOnDestroy(): void {
    this.next()
    this.complete()
  }

}
// 组件中使用

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.less'],
  providers: [DestroyService]
})
export class AppComponent {
  constructor(private destroy$: DestroyService) {
    fromEvent(document, 'click').pipe(takeUntil(this.destroy$)).subscribe(() => {})
  }
}

个人觉得在 vue 和 angular 中使用rxjs取消订阅封装中,vue的封装和使用更简单,封装原理一样,订阅一个流,在不用时取消(组件销毁)

注意: 当一个流 报错(error) 或 完成了(complete)表示就已停止,这时订阅也就停了,所以这时的订阅可以不取消

以上如有不对的,欢迎留言交流学习

在线举报