前言
介紹完 RxJS 兩大概念 Observable 和 Subject 之後, 篇幅最大的就是各種 Operators 了.
這篇先介紹比較簡單的 Filter Operators. 請先看完上面 2 篇哦. 因爲例子會用到一些之前介紹過的方法.
參考
超級多, 這裏我只介紹我自己有用過, 或者以後有可能會用到的. 順序會依照我最常用和相關性排.
filter
const source = from([1, 2, 3, 4]); const odd$ = source.pipe(filter(v => v % 2 !== 0)); const even$ = source.pipe(filter(v => v % 2 === 0)); odd$.subscribe(v => console.log(v)); // 1..3 (only value 1 and 3 will call) even$.subscribe(v => console.log(v)); // 2..4
之前提過, stream 和 Array 很像. 所以它也有 filter 的概念.
每當發佈一個 new value, 先經過 filter, 如果 ok, 最終 subscriber 才能接收到.
first
顧名思義, 就是第一個發佈的 value 才接收
const source = from([1, 2, 3, 4]);
source.pipe(first(v => v % 2 !== 0)).subscribe(v => console.log(v)); // 1
此外, filter 可以傳入 filter 參數哦. 上面這個表示, 第一個 odd value 才接收.
last
有 first 自然就有 last. 不過 last 比較可愛的地方是, 怎麼知道是最後一個呢?
所以它需要一個 complete 才能確保哪一個是最後一個.
const obs = new Observable<number>(subscriber => { subscriber.next(1); subscriber.next(2); subscriber.next(3); subscriber.next(4); subscriber.complete(); // 重要 }); obs.pipe(last(v => v % 2 !== 0)).subscribe(v => console.log(v)); // 3
如果少了 complete, subscriber 將永遠不會接收到 last value.
elementAt
指定拿第幾個發佈值.
const obs = from([1, 2, 3, 4]); obs.pipe(elementAt(2)).subscribe(v => console.log(v)); // 3
如果 Observable 結束都沒有達到指定的 index, 它會報錯, 可以通過設置 default value 解決這個報錯.
const obs = from([1, 2, 3, 4]); obs.pipe(elementAt(100, 'default value')).subscribe(v => console.log(v)); // 'default value'
skip
顧名思義, 就是跳過幾個
const source = from([1, 2, 3, 4]); source.pipe(skip(2)).subscribe(v => console.log(v)); // 3..4
skip(2) 表示前面 2 次發佈不接收.
skipLast
const source = from([1, 2, 3, 4]); source.pipe(skipLast(2)).subscribe(v => console.log(v)); // 1..2
和 last 一樣, Observable 一定要有 complete 哦, 不然不知道哪個算 last.
skipUntil
一直 skip 直到參數 Observable 發佈
const obs = interval(1000); // 0..1..2..3..4..5 obs.pipe(skipUntil(timer(4000))).subscribe(v => console.log(v)); // 3..4..5
obs 每一秒發佈一下. skipUntil 的參數 Observable 4 秒後發佈. 於是前面 4 秒的值都被 skip 掉了.
爲什麼 3 沒有被 filter, 那時因爲 interval 是慢一拍的. 之前文章有解釋過了.
如果覺得亂可以改成
const obs = timer(0, 1000); // 0..1..2..3..4..5 obs.pipe(skipUntil(timer(4000))).subscribe(v => console.log(v)); // 4..5
skipWhile
const source = from([1, 2, 3, 4]); source.pipe(skipWhile(v => v <= 2)).subscribe(v => console.log(v)); // 3..4
skip 和 skipUntil 都沒有辦法依據 value 來判斷是否持續 skip. 而 skipWhilte 補上了這個缺失.
上面的例子表示, 只要 value <=2 就一直 skip 下去, 直到 value > 2 出現, 纔開始接收.
take
take 和 skip 是相反的概念
skip(2) 表示頭 2 個 "不要", 剩餘的都 "要"
take(2) 表示頭 2 個 "要", 剩餘的 "不要"
const source = from([1, 2, 3, 4]); source.pipe(take(2)).subscribe({ next: v => console.log(v), // 1..2 complete: () => console.log('complete'), // complete when dispatch 2 });
另外, 由於剩餘的都不要了, 所以它會提前結束 stream displose Observable.
takeLast
const source = from([1, 2, 3, 4]); source.pipe(takeLast(2)).subscribe(v => console.log(v)); // 3..4
只拿最後 2 個. 但凡有 "last" 的 operators, Observable 記得要 complete 哦, 而且 complete 了以後 subscriber 纔會接收到 value.
takeUntil
const obs = timer(0, 1000); // 0..1..2..3..4..5 obs.pipe(takeUntil(timer(3000))).subscribe(v => console.log(v)); // 0..1..2
一直拿到 3 秒, 往後的都不要了
takeWhile
const source = from([1, 2, 3, 4]); source.pipe(takeWhile(v => v <= 2)).subscribe(v => console.log(v)); // 1..2
依據 value 判斷要拿到什麼時候停.
distinct
const source = from([1, 2, 3, 3, 2, 1, 4]); source.pipe(distinct()).subscribe(v => console.log(v)); // 1..2..3..4
但凡出現過的 value 就不再第二次接收.
key selector
value 的 compare 方式是 ===
這種 compare 最怕就是遇到對象了. 因爲有時候對象指針雖然不同, 但其實是同一個 value 來的.
const source = from([ { id: 1, name: 'Derrick' }, { id: 2, name: 'Xinyao' }, { id: 1, name: 'Derrick' }, ]); source.pipe(distinct(v => v.id)).subscribe(v => console.log(v)); // 1..2
這時就可以提供一個 key selector 函數. 這樣對比的方式從 v === v 變成了 v.id === v.id
distinctUntilChanged
distinct 是指, 但凡過往只要出現過, 那麼就不接收.
distinctUntilChanged 則是, 如果上一個 value 和當前這一個一樣的話, 那就不接收. 重點在它只 care 上一個而且, distinct 是看過往所有的 value.
const source = from([1, 2, 3, 3, 3, 2, 1, 4, 3]); source.pipe(distinct()).subscribe(v => console.log(v)); // 1..2..3..4 source.pipe(distinctUntilChanged()).subscribe(v => console.log(v)); // 1..2..3..2..1..4..3
distinctUntilChanged 只 filter 了中間區域的兩個 3. 因爲中間連續了三個 3.
key selector and comparator
distinctUntilChanged 比 distinct 更 cutomize, 它除了可以提供 key selector, 甚至連 compare 的方法想從 === 換成 == 都可以
distinctUntilKeyChanged
source.pipe(distinctUntilKeyChanged('id')).subscribe(v => console.log(v));
它底層只是調用了 distinctUntilChanged 而已. 沒有必要學這個, 用 distinctUntilChanged 就可以了.
debounceTime
debounceTime 常用於限制過於頻密的發佈, 比如 user typing ajax search
當用戶連續打字的時候, 我們不會發送 ajax search, 直到用戶停止輸入後才發 ajax.
const subject = new Subject<string>(); subject.pipe(debounceTime(2000)).subscribe(v => console.log(v)); subject.next('a'); await delayAsync(1000); subject.next('b'); // 還不到 2 秒又發佈了, a 被取消接收 await delayAsync(1000); subject.next('c'); // 還不到 2 秒又發佈了, b 被取消接收 // 2 秒後不再有發佈, c 被接收
debounceTime(2000) 表示, Subject 發佈後, 會先觀察 2 秒, 如果沒有下一個發佈, 那麼這個值將被接收, 如果 2 秒內又有新的發佈, 那就就丟棄上一個值, 繼續觀察下一個 2 秒.
所以, 如果每一次發佈間隔都小於 2 秒, 那麼觀察會一直進行下去, 不會有任何接收.
debounce
參考: RxJS 學習系列 10. 過濾操作符 debounce,debounceTime,throttle,throttleTime
debounce 比較少用到, 它和 debounceTime 類似, 都是用於限制頻密發佈.
但它比較厲害的地方是可以控制 duration. debounceTime 只能 set 死一個秒. debounce 可以動態控制
const subject = new Subject<string>(); const durationSelector = (value: string) => timer(2000); subject.pipe(debounce(durationSelector)).subscribe(v => console.log(v));
每當 Subject 發佈, durationSelector 就會被調用, 它會拿到 Subject 發佈的值, 然後返回一個 duration observable
這個 duration observable 就是一個觀察期. 在 duration observable 發佈以前, 如果 subject 又發佈, 那麼上一次 subject 發佈的值就被丟棄. 然後 durationSelector 再次被調用. 等待下一個觀察期.
throttleTime
throttleTime 和 debounceTime 類似, 都是限制頻密發佈的.
區別在於, 當 Subject 發佈後, 會立馬接收, 然後進入關閉期. 在關閉期間, 如果 Subject 又發佈, 那麼就拒絕接收 (filter 掉)
const subject = new Subject<string>(); subject.pipe(throttleTime(1000)).subscribe(v => console.log(v)); subject.next('v'); // 接收 subject.next('v'); // 拒絕 (因爲在 1 秒內又發佈了) await delayAsync(1000); subject.next('v2'); // 接收 (因爲已經過了 1 秒的關閉期)
throttle
throttle & throttleTime 的關係就和 debounce & debounceTime 關係一樣.
就是多了一個 durationSelector 可以動態聲明關閉期.
auditTime
auditTime和 debounceTime 類似, 都是限制頻密發佈的.
區別在於, auditTime 不會 reset 觀察期.
const subject = new Subject<string>(); subject.pipe(auditTime(3000)).subscribe(v => console.log(v)); // v3..v5 subject.next('v1'); // 拒絕 await delayAsync(1000); subject.next('v2'); // 拒絕 await delayAsync(1000); subject.next('v3'); // 接收 await delayAsync(1000); subject.next('v4'); // 拒絕 await delayAsync(1000); subject.next('v5'); // 接收
觀察期是 3 秒, 雖然 Subject 一直在發佈, 但是不會 reset 觀察期, 所以 3 秒鐘一到 v3 就接收到了.
換成是 debounceTime 的話, v1, v2, v3, v4 都會被拒絕, 因爲每一次觀察期會不斷被 reset (一直重新觀察 3 秒)
audit
audit & auditTime 的關係就和 debounce & debounceTime 關係一樣.
就是多了一個 durationSelector 可以動態聲明觀察期.
debounceTime, throttleTime, auditTime 總結
debounceTime (delay and keep postpone) : 發佈...進入觀察期(不馬上接收)...再發布...丟棄上一個, 重新觀察期(不馬上接收)...觀察期結束(接收)
throttleTime (immediately + skip) : 發佈...關閉期(馬上接收)...再發布...skip...關閉期結束...再發布...關閉期(馬上接收)...循環
auditTime (delay only, no keep postpone) : 發佈..觀察期(不馬上接收)...再發布...丟棄上一個(但不開啓新的觀察期了)...觀察期結束(接收)
sampleTime
sampleTime 和上面的 debounceTime 它們沒有關係. 用途不一樣.
sampleTime 是按時去取值 (取樣) 的概念
(async () => { const subject = new Subject(); subject .pipe(sampleTime(3000)) .subscribe(v => console.log('sample time ' + parseInt(performance.now().toString()), v)); await delayAsync(2000); subject.next(1); await delayAsync(2000); subject.next(2); await delayAsync(5000); subject.next(3); })();
sampleTime(3000) 表示每個 3 秒, 去檢查 subject 是否有發佈新值, 如果有那麼就發佈, 如果沒有就繼續等下一個 3 秒在檢查.
效果
3 秒的時候拿到 1
6 秒的時候拿到 2
9 秒的時候 subject 沒有發佈新值, 所以也就沒有發佈了
12 秒的時候拿到 3
sample
sample 和 sampleTime 功能是一樣的, 區別在於檢測的 timing
sampleTime 只能設定間隔時間, sample 則可以傳入任何 Observable, 所以可以傳入 click$, 這樣每次點擊的時候就去取樣.
沒有介紹到的 Filter Operators
ignoreElements
single
一句話總結
filter : 條件過濾
first : 拿第一個
last : 拿最後一個, 記得 complete
elementAt : 拿第 n 個
skip : 跳過頭 n 的
skipLast : 跳過最後 n 個, 記得 complete
skipUntil : 一直跳過, 直到 Observable 發佈
skipWhile : 跳過依據 value
take : 只拿頭幾個, 拿夠了自動 complete
takeLast : 只拿最後幾個, 記得 complete
takeUntil : 拿頭幾個, 直到 Observable 發佈
takeWhile : 拿投幾個, 依據 value 判斷
distinct : 但凡出現過就不接收
distinctUntilChanged : 和上一個一樣就不接收
distinctUntilKeyChanged : 只是 distinctUntilChanged 語法糖
debounceTime : delay and keep postpone
debounce : dynamic 控制 time
throttleTime immediately + skip
throttle : dynamic 控制 time
auditTime : delay only, no keep postpone
audit : dynamic 控制 time
sampleTime : 定時取樣, Observable 沒有發佈就 skip
sample : 控制 time