Angular中深度集成了Rxjs,只要你使用Angular框架,你就不可避免的会接触到RxJs相关的知识。
在Android开发中,绝大多数的Android开发者都用过RxJava、RxAndroid或RxKotlin相关库。因其强大的操作符 以及 方便的线程切换 给我们日常开发提供了极大的便利。
但是,可能是前端并不像强类型语言那么严格,即使代码写的有点小问题,也是能照样运行,不仔细排查也发现不了什么影响。
在最近接触的Angular项目中,发现前端的小伙伴们很少去了解RxJs的原理,导致写的项目中,出现了很多只订阅不取消代码,以及 Observable 和 Subject 区别是什么都分不清的情况,导致项目运行时,一个事件发射出去,结果一堆订阅接收到了,甚至多次运行后,会出现重复执行几十次上百次的情况。
之前写过一篇在Android中如何优雅的处理自定取消订阅的方法,感兴趣的可以看下: AutoDispose代替RxLifecycle优雅的解决RxJava内存泄漏问题
本篇博客我们来看一下在Angular中如何优雅的处理RxJs自动取消订阅的问题。
方式一 (不推荐)
首先是常规用法,
我们在使用 subscribe 的时候会返回一个Subscription 对象,如下
该对象提供一个 unsubscribe() 方法,如下
我们只需要调用该方法即可取消订阅,一般都是在OnDestory中取消订阅。大致代码如下
ngOnDestroy(): void {
this.subject.unsubscribe()
}
有时候我们不能仅限于会用,应该要知道为什么这样写。
以Subject为例,我们可以大致瞧一眼,unsubscribe() 代码都写了些什么,为什么调用这个方法后就不会收到next() 发射的数据流了。
代码一目了然,并没有什么高大上的代码,其实就是unSubscribe的时候将 closed
和 isStopped
置为true, 在 next 的时候判断一下,如果已经close或者stop的话,就不发射数据了。
好了,扯远了,回归主题,为什么不建议这么写。
因为当你一个类中的 Subscription 特别多的时候,那么这种写法就很麻烦,每个都要 unsubscribe() 一遍,显然是不合适的。
方式二
使用takeUntil 操作符代替unsubscribe
我们先来看一下 takeUntil 操作符是干嘛的,以下是复制的源码及注释。
/**
* Emits the values emitted by the source Observable until a `notifier`
* Observable emits a value.
*
* Lets values pass until a second Observable,
* `notifier`, emits a value. Then, it completes.
*
* 
*
* `takeUntil` subscribes and begins mirroring the source Observable. It also
* monitors a second Observable, `notifier` that you provide. If the `notifier`
* emits a value, the output Observable stops mirroring the source Observable
* and completes. If the `notifier` doesn't emit any value and completes
* then `takeUntil` will pass all values.
*
* ## Example
* Tick every second until the first click happens
* ```ts
* import { fromEvent, interval } from 'rxjs';
* import { takeUntil } from 'rxjs/operators';
*
* const source = interval(1000);
* const clicks = fromEvent(document, 'click');
* const result = source.pipe(takeUntil(clicks));
* result.subscribe(x => console.log(x));
* ```
*
* @see {@link take}
* @see {@link takeLast}
* @see {@link takeWhile}
* @see {@link skip}
*
* @param {Observable} notifier The Observable whose first emitted value will
* cause the output Observable of `takeUntil` to stop emitting values from the
* source Observable.
* @return {Observable} An Observable that emits the values from the source
* Observable until such time as `notifier` emits its first value.
* @method takeUntil
* @owner Observable
*/
export function takeUntil<T>(notifier: Observable<any>): MonoTypeOperatorFunction<T> {
return (source: Observable<T>) => source.lift(new TakeUntilOperator(notifier));
}
注释的大概意思就是,当takeUntil里面的notifier接收到值时,就会终止数据流。这样一来,就达到了我们的目的。
大致用法如下:
import {Component, OnDestroy, OnInit} from '@angular/core';
import {Subject} from 'rxjs';
import {takeUntil} from 'rxjs/operators';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit, OnDestroy {
private subject = new Subject();
private $destory = new Subject<boolean>();
constructor() {
}
/**
* 组件初始化完毕时调用
*/
ngOnInit(): void {
this.subject
.pipe(
takeUntil(this.$destory)//需要注意takeUntil要放在最后
)
.subscribe(data => {
console.log('data:', data);
});
}
ngOnDestroy(): void {
//this.subject.unsubscribe()
this.$destory.next(true);
this.$destory.unsubscribe();
}
}
这样一来即使一个组件中有很多订阅,也不用在 ngOnDestroy() 中写很多unSubscribe。
但是这样还是有些麻烦,毕竟每个组件都要写
private $destory = new Subject<boolean>();
ngOnDestroy(): void {
//this.subject.unsubscribe()
this.$destory.next(true);
this.$destory.unsubscribe();
}
这种重复代码。下面我们基于方式二稍微优化一下。
方式二优化(推荐)
我们可以写一个BaseComponent,将一些使用率很高的模板代码放到BaseComponent中,其他组件继承该组件即可。
例如:
BaseComponent
import {Component, OnDestroy, OnInit} from '@angular/core';
import {Subject} from 'rxjs';
@Component({
selector: 'app-base',
templateUrl: './base.component.html',
styleUrls: ['./base.component.css']
})
export class BaseComponent implements OnInit, OnDestroy {
$destory: Subject<boolean> = new Subject<boolean>();
constructor() {
}
ngOnInit(): void {
}
ngOnDestroy(): void {
this.$destory.next(true);
this.$destory.unsubscribe();
}
}
让我们的组件继承BaseComponent,然后删除无用的模板代码
import {Component} from '@angular/core';
import {Subject} from 'rxjs';
import {takeUntil} from 'rxjs/operators';
import {BaseComponent} from './base/base.component';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent extends BaseComponent {
private subject = new Subject();
constructor() {
super();//继承后要加上super()
}
/**
* 组件初始化完毕时调用
*/
ngOnInit(): void {
this.subject
.pipe(
takeUntil(this.$destory)//需要注意takeUntil要放在最后
)
.subscribe(data => {
console.log('data:', data);
});
}
}
其他组件同样继承BaseComponent即可。
我们还可以添加很多模板代码放到BaseComponent中,这样一来就可以减少很多无用的代码了,而且维护起来更加容易。
如果你觉得本文对你有帮助,麻烦动动手指顶一下,可以帮助到更多的开发者,如果文中有什么错误的地方,还望指正,码字不易,转载请注明转自喻志强的博客 ,谢谢!