在angular項目中我們不可避免的要使用RxJS
可觀察對象(Observables
)來進行訂閱(Subscribe
)和退訂(Unsubscribe
)操作;
概述
我們的每個angular項目中都會用到RxJS
, RxJS
在我們的angular app中對數據流和性能有非常大的影響。
為了避免內存泄漏,在適當的時機對可觀察對象進行退訂是非常重要的; 本文會向你展示各種在angular組件中退訂可觀察對象的方法!
首先,我們創建一個幫助類來幫我們創建的訂閱對象(Subscription
)
@Injectable({
providedIn: 'root',
})
export class DummyService {
getEmissions(scope: string): Observable<string> {
return Observable.create((observer) => {
console.log(`${scope} Subscribed`);
const subscription: Subscription = timer(0, 1000)
.pipe(
map((n) => `${scope} Emission #${n}`),
tap(console.log)
)
.subscribe(observer);
return () => {
subscription.unsubscribe();
console.log(`${scope} Unsubscribed`);
};
});
}
}
我們的幫助類有一個getEmissions
方法, 它接受一個scope
參數來記錄日志, 它的返回值是一個會每秒發出 ${scope} Emission #n
字符串的可觀察對象.
方式一 "常規"的取消訂閱的方式
最簡單的訂閱和取消訂閱一個可觀察對象的方式是在 ngOnInit
方法中訂閱可觀察對象(Observable
), 然后在組件類中創建一個類屬性用來保存這個訂閱(Subscription
), 並在 ngOnDestroy
中取消對可觀察對象對訂閱. 簡單起見, 我們可以使用Subscription.EMPTY
來初始化一個訂閱對象(Subscription
), 這樣可以防止在取消訂閱時遇到空引用對問題.
@Component({
selector: 'app-regular',
template: `<div>{{ emission }}</div>`,
})
export class RegularComponent implements OnInit, OnDestroy {
emission: string;
/*
Note: we initialize to Subscription.EMPTY to avoid null checking within ngOnDestroy
*/
private subscription: Subscription = Subscription.EMPTY;
constructor(private dummyService: DummyService) {}
ngOnInit(): void {
this.subscription = this.dummyService
.getEmissions('Regular')
.subscribe((emission) => (this.emission = emission));
}
ngOnDestroy(): void {
this.subscription.unsubscribe();
}
}
為了驗證代碼有效我們在三秒后從DOM
中移除這個組件
如上所述, 這是最基本對取消訂閱的方式, 如果我們的組件類中只有一個訂閱對象(Subscription
), 這種方式沒什么問題. 但是當我們有多個訂閱對象(Subscription
)時, 針對每一個我們都需要在組件類中創建一個字段保存這個對象的的引用並在 ngOnDestroy
中調用 unsubscribe
來取消訂閱.
方式二 使用 Subscription.add 方法
RxJS的訂閱類(Subscription
)內建了 Subscription.add 方法允許我們使用單個訂閱對象的實例(Subscription instance
)來簡化我們操作多個訂閱對象的.
首先, 在組件類中使用new Subscription()
實例化創建一個字段, 然后調用該實例的 Subscription.add
方法, 最后在 ngOnDestroy
中取消訂閱.
@Component({
selector: 'app-add',
template: `
<div>{{ emissionA }}</div>
<div>{{ emissionB }}</div>
`,
})
export class AddComponent implements OnInit, OnDestroy {
emissionA: string;
emissionB: string;
private subscription: Subscription = new Subscription();
constructor(private dummyService: DummyService) {}
ngOnInit(): void {
this.subscription.add(
this.dummyService
.getEmissions('[Add A]')
.subscribe((emission) => (this.emissionA = emission))
);
this.subscription.add(
this.dummyService
.getEmissions('[Add B]')
.subscribe((emission) => (this.emissionB = emission))
);
}
ngOnDestroy(): void {
this.subscription.unsubscribe();
}
}
打開瀏覽器控制台, 我們可以看到兩個訂閱對象:
使用這種方式, 我們可以使用RsJS內建的方法輕松的取消訂閱多個可觀察對象而不必在組件類創建多個字段保存訂閱對象的引用.
方式三 AsyncPipe
Angular內置了許多非常有用的管道(pipe), 其中一個就是AsyncPipe. AsyncPipe
接受一個可觀察對象並在組件生命周期結束時(ngOnDestroy
)自動取消訂閱.
與前兩個示例不同, 這里我們不需要在組件中手動取消訂閱, 而是將可觀察對象(Observable
)傳遞個 AsyncPipe
:
@Component({
selector: 'app-async',
template: `<div>{{ emissions$ | async }}</div>`
})
export class AsyncComponent implements OnInit {
emissions$: Observable<string>;
constructor(private dummyService: DummyService) {}
ngOnInit(): void {
this.emissions$ = this.dummyService.getEmissions('[Async]');
}
}
在我看來, 這是在Angular中使用可觀察對象(Observables
)最簡明的方式. 你只需創建可觀察對象(Observables
)然后Angular會幫助你進行訂閱和取消訂閱.
方式4 takeUntil 操作符
RxJS包含許多有用的操作符, takeUntil就是其中之一. 像這個操作符的簽名一樣, takeUntil 接受一個會發出取消訂閱源可觀察對象通知的可觀察對象(notifier
).
在我們的示例中, 我們希望在組件被銷毀后發出通知, 所以我們給組件類添加一個叫 componentDestroyed$
的字段, 它的類型是 Subject<void>
, 這個字段承擔了通知人(notifier)
的角色.
然后我們只需在ngOnDestroy
發出"通知"即可, 最終的代碼像下面這樣:
@Component({
selector: 'app-until',
template: `<div>{{ emission }}</div>`,
})
export class UntilComponent implements OnInit, OnDestroy {
emission: string;
private componentDestroyed$: Subject<void> = new Subject<void>();
constructor(private dummyService: DummyService) {}
ngOnInit(): void {
this.dummyService
.getEmissions('takeUntil')
.pipe(takeUntil(this.componentDestroyed$))
.subscribe((emission) => (this.emission = emission));
}
ngOnDestroy(): void {
this.componentDestroyed$.next();
}
}
與之前常規
的方式相比, 這種方式在我們有多個訂閱對象時不必在組件類中創建多個字段保存對訂閱對象的引用. 我們只需在管道中加入 takeUntil(componentDestroyed$)
即可, 剩下的RxJS會幫我們完成.
方式五 SubSink 庫
SubSink是Ward Bell寫的一個很棒的庫, 它使你可以優雅的在你的組件中取消對可觀察對象的訂閱.
首先, 通過npm i subsink
或yarn add subsink
安裝SubSink. 然后在組件類中創建一個SubSink
類型的字段.
SubSink有兩種方式, 一種是簡單技術(使用sink
屬性設置器), 另一種是 數組/添加(Array/Add)技術.
使用簡單技術只需要使用sink
設置器屬性即可. 使用_數組/添加(Array/Add)技術_的話代碼類似RxJS原生的Subscription.add
為每一種方式創建一個訂閱對象, 我們的組件類看起來像下面這樣
@Component({
selector: 'app-sink',
template: `
<div>{{ easyEmission }}</div>
<div>{{ arrayAddEmission }}</div>
`,
})
export class SinkComponent implements OnInit, OnDestroy {
easyEmission: string;
arrayAddEmission: string;
private subs = new SubSink();
constructor(private dummyService: DummyService) {}
ngOnInit(): void {
/* 使用簡單技術 */
this.subs.sink = this.dummyService
.getEmissions('[Easy Technique]')
.subscribe((emission) => (this.easyEmission = emission));
/* 使用數組/添加(Array/Add)技術 */
this.subs.add(
this.dummyService
.getEmissions('[Array/Add Technique]')
.subscribe((emission) => (this.easyEmission = emission))
);
}
ngOnDestroy(): void {
this.subs.unsubscribe();
}
}
方式六 until-destroy 庫
注意: 這個庫在Pre Ivy Angular上行為不同, 更多信息請訪問文檔
until-destroy是ngneat許多很棒的庫之一, 它使用UntilDestroy
裝飾器來確認哪些字段的是訂閱對象(Subscriptions
)並在組件銷毀時取消訂閱它們;
我們還可以不通過組件類字段, 而是使用_until-destroy_定義的叫untilDestroyed
的RxJS操作符來取消訂閱.
要使用它我們需要給組件類加上 UntilDestroy
裝飾器, 然后在可觀察對象管道中加入 untilDestroyed
操作符:
@UntilDestroy()
@Component({
selector: 'app-destroyed',
template: `<div>{{ emission }}</div> `,
})
export class DestroyedComponent implements OnInit {
emission: string;
constructor(private dummyService: DummyService) {}
ngOnInit(): void {
this.dummyService
.getEmissions('[UntilDestroy]')
.pipe(untilDestroyed(this))
.subscribe((emission) => (this.emission = emission));
}
}
總的來說, until-destroy是個非常強大的庫, 他可以幫你自動取消對可觀察對象的訂閱.
此外, until-destroy還有許多其他在本文中沒有進行說明的特性, 所以趕快去看看它們的github倉庫吧!
總結
上面我們已經看到來許多訂閱和退訂可觀察對象方式, 每個都各有各的優劣並且有着不同的編碼風格.
但是最重要是不管我們選擇那種方式, 我們都要保持編碼風格的一致