在 Angular 中請這樣用 RxJS
本篇只是閱讀原文後的譯文
原文出處: https://blog.strongbrew.io/rxjs-best-practices-in-angular
Stream 加上 $
suffix
這是一個由 Cycle.js 開始的慣例
Angular 官方也建議,在 Observable
型別的變數後面加上 $
,這樣一看就知道是不是 observable
1 | <!-- bad --> |
使用 pure functions
pure functions 的定義
- functions 不會改動任何的外部狀態
- functions 不依賴 input 以外的 outside state,一樣的 input 會得到一樣的 output
此為 functional programming 的基礎,當我們在 reactive flow 時,遵循這個原則可以讓我們不會被 outside state 干擾,更專注於 reactive 的行為上
記得 unsubscribe()
不要造成 memory leak
常用 3 招 unsubscribe 所有的 subscriptions
使用 array
1
2
3
4
5
6
7
8
9
10
11
12import { Subscription } from 'rxjs';
class AppComponent implements OnDestroy {
private subscriptions = Subscription[];
this.subscriptions.push(observable1$.subscribe());
this.subscriptions.push(observable2$.subscribe());
this.subscriptions.push(observable3$.subscribe());
ngOnDestroy() {
this.subscriptions.forEach(s => s.unsubscribe());
}
}使用
takeUntil()
operator1
2
3
4
5
6
7
8
9
10
11class AppComponent implements OnDestroy {
private destroy$ = new Subject();
this.observable1$.pipe(takeUntil(this.destroy$)).subscribe());
this.observable2$.pipe(takeUntil(this.destroy$)).subscribe());
this.observable3$.pipe(takeUntil(this.destroy$)).subscribe());
ngOnDestroy() {
this.destroy$.next(true);
}
}使用 https://github.com/wardbell/subsink
1
2
3
4
5
6
7
8
9
10
11class AppComponent implements OnDestroy {
private subs = new SubSink();
this.subs.sink = observable1$.subscribe();
this.subs.sink = observable2$.subscribe();
this.subs.sink = observable3$.subscribe();
ngOnDestroy() {
this.subs.unsubscribe();
}
}
不要重複訂閱
1 | // bad |
loadData()
如果會被重複使用到就造成重複訂閱了
1 | // good |
不要 nested subscribes
1 | // bad |
1 | // good |
再來一個 Akita 的例子
1 | // bad |
憋不住寫出 nested subscribes,請用 switchMap
, concatMap
, mergeMap
, exhaustMap
重構這坨糙 code
可以用 async
pipeline 就不要自己 subscribe
好處
- 自動幫你 on init subscribe
- 自動幫你 on destroy unsubscribe
- code 變少了 更好看
1 | ({ |
不要從父組件傳 stream 給子組件
父組件訂閱 stream 後,直接傳值給子組件
這樣一來就不用每個子組件都訂閱一次,使用更少記憶體,且邏輯也更清晰
1 | // bad |
1 | // good |
不要從 component 傳 stream 給 service
原本一個 stream 從 service 出發,到 component 被 subscribe
services > component
這個時候你又把 stream 作為參數傳回 service,如果這個 service 又被其他 component 使用
services > component > service > components > …
此時這個 Stream 的生命週期就沒完沒了了
1 | // bad |
正確做法應該是
1 | // good |
少用 BehaviorSubject
與 Akita 的 getValue()
當你使用 getValue()
的瞬間,你就已經 not thinking reactive 了
保持 Clean code
pipe 內的 operators 對齊好
1
2
3
4
5foo$.pipe(
map(...)
filter(...)
tap(...)
)stream 邏輯太複雜的時候抽到另一個 stream 中,可以用
subject$.next()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23// Listening on save button click
this.subscriptions.push(
this.onSaveBtnClick$.subscribe(data => {
const user: User = { ...data };
!user.id ? this.createRecord$.next(user) : this.updateRecord$.next(user);
})
);
// Listening on record create
this.subscriptions.push(
this.createRecord$.pipe(
mergeMap(user => this.userService.create(user))
)
.subscribe(_ => console.info('user created'))
);
// Listening on record update
this.subscriptions.push(
this.updateRecord$.pipe(
mergeMap(user => this.userService.update(user.id, user))
)
.subscribe(_ => console.info('user created'))
);operator 中的邏輯太複雜的時候,可以抽到
private
method 中單行能解決,就不用
{}
1
2
3
4
5
6
7// bad
observable$.subscribe(result => {
console.log(result)
})
// good
observable$.subscribe(result => console.log(result))
在 Angular 中請這樣用 RxJS
https://blog.yang-hong-xin.com/using-rxjs-this-way-in-angular/