RxJs 常用運算子介紹
前言
RxJs提供相當多的Operator
官網可以查看所有Operator與使用方式
基本上只要熟悉幾個常用的,大部份的邏輯應足以處理!
剩下則邊用邊學即可
Operator的命名相當語義化,可以很明確看出其功能
經過各種組合後,就可以組出一條相當漂亮且易於維護的程式碼
之前提過RxJs就像是一條工廠流水線
可以至RxViz將流水線視覺化!
對於初學來說,會是一條很好的學習方式
尤其像mergeMap
這種併發型的Operator,就連熟手有時也會混亂
建立Observable
將資料轉成Observable,常用的有下列函式
若為單純變數,不論是from
、of
,結果都一樣
而他們之間最大的差別就是:from
會把可迭代的(iterator
)資料逐一送出of
則不論是否可迭代(iterator
),一律整份送出
from
from('TEST').subscribe(console.log); // 輸出'TEST'
from([1, 2, 3]).subscribe(console.log); // 逐一輸出: 1--2--3
of
from('TEST').subscribe(console.log); // 輸出'TEST'
from([1, 2, 3]).subscribe(console.log); // 輸出完整陣列: [1, 2, 3]
fromEvent
通常是前端使用
針對特定事件(如click
)轉換成Observable
fromEvent(document, 'click').subscribe(console.log); // 輸出 click事件的完整物件
放在pipe裡的Operator
tap
用來處理與流水線主要業務無關、但須動作的事
通常用來處理一些額外動作,但不會變動到資料集的事務
如寫log、回傳前須異動DB特定flag欄位…之類的
最大宗使用都是在寫log
在Angular裡不太會用到
因若需要debug,直接在相關邏輯裡加console.log
就好了
不太會特地import debug完後又刪掉
但在後端Nest.Js就滿有機會的了,若商業邏輯完全使用RxJs實現
那tap
則可以將寫log之事獨立出來,將function更加的原子化!
from([1, 2, 3]).pipe(
tap(console.log), // (靜態寫法)印出1, 2, 3
tap(value => console.log(vaule)), // (傳參寫法)印出1, 2, 3
).subscribe();
map
RxJs裡最常使用的運算子!
不知道該用什麼運算子,用map
就對了
自己墊邏輯進去一切都可做
命名也很直覺,在各語言functional programming
裡一定會見到
from([1, 2, 3]).pipe(
map(val => {
if (val < 2) {
return val;
}
return null;
})
).subscribe(console.log); // 逐一輸出: 1, null, null
filter
第二常使用的運算子,用以過濾掉指定條件的資料(或取出特定資料)
一樣在各語言functional programming
裡也會見到
from([1, 2, 3]).pipe(
map(val => {
if (val < 2) {
return val;
}
return null;
}),
filter(val => val !== null)
).subscribe(console.log); // 逐一輸出: 1
與takeWhile, takeUntil的區別
從函式命名上可以感覺與filter
很像,但似乎比filter
命名更加精確!
條件符合時才拿取(take
),但實際使用時,卻會覺得怪怪的
怎麼只抓一次就不抓了?!
take
與filter
最大的區別在於:取完後就complete
了!
在RxJs中,complete
就代表不在「觀察」
永遠不會再接收、處理任何資料!
所以大部份情況下還是乖乖用filter
就好嘍!
toArray與groupBy
在後端做資料分組時較易用上
前端反而不太用到
而這2個很常組合使用
搭配mergeMap
const array = [
{id: 1, shopId: 'a', category: 'AAA', name: 'Andy', birth: '1990/06/25'},
{id: 2, shopId: 'b', category: 'BBB', name: 'Ben', birth: '1989/01/01'},
{id: 3, shopId: 'a', category: 'AXX', name: 'Amy', birth: '1990/06/01'},
{id: 4, shopId: 'b', category: 'BXX', name: 'Brandon', birth: '1989/05/06'},
{id: 5, shopId: 'a', category: 'AAA', name: 'Anderson', birth: '1991/12/06'},
];
from(array).pipe(
groupBy(val => val.shopId),
mergeMap(group => group.pipe(toArray())
);
// 得到結果如下:
[
[
{id: 1, shopId: 'a', category: 'AAA', name: 'Andy', birth: '1990/06/25'},
{id: 3, shopId: 'a', category: 'AXX', name: 'Amy', birth: '1990/06/01'},
{id: 5, shopId: 'a', category: 'AAA', name: 'Anderson', birth: '1991/12/06'},
],
[
{id: 2, shopId: 'b', category: 'BBB', name: 'Ben', birth: '1989/01/01'},
{id: 4, shopId: 'b', category: 'BXX', name: 'Brandon', birth: '1989/05/06'}
]
]
進階用法可參考補充資料:RxJs groupBy 多條件分組
mapTo
不在乎取得結果,直接強制轉成特定值
當然用map
亦可做到,但mapTo
語意更明顯,寫法更精簡
fromEvent(document, 'click').pipe(
// map(event => 'clicked!'), // map 寫法會長這樣,省掉了暱名函式!
mapTo('clicked!') // 不在乎點擊事件的event內容,一律轉成字串'clicked!'
).subscribe(console.log);
throwError與catchError
相當於語言裡的try-catch
寫法
而throwError
則是將錯誤轉換成Observable,供Observer
裡的error
接收
通常會搭配catchError
一起出現
要注意的是:catchError
接到後,須回傳Observable
from([1, 2, 3, 4, 5]).pipe(
map(value => {
if (value < 3) {
return value;
}
throw new Error('大於3了!!');
}),
catchError(error => throwError(error)),
).subscribe(
result => console.log(result), // 輸出1 -> 2
error => console.log(`errorMessage: ${error}`)); // 輸出「errorMessage: 大於3了!!」