5個RxJs有趣範例

之前提到過RxJs,這篇文章主要是從Rx’Library官方的練習題挑出5個來介紹,都蠻好理解的
有興趣的朋友也可以直接試試看,run起來對了才能解鎖下一題,挺有趣的


Retrieve id, title, and a 150x200 box art url for every video

首先要先介紹一下Function Programming編程精神,data的結構大概是以下這樣的Array

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
var movieLists = [
{
name: "Instant Queue",
videos: [
{
"id": 70111470,
"title": "Die Hard",
"boxarts": [
{ width: 150, height: 200, url: "http://cdn-0.nflximg.com/images/2891/DieHard150.jpg" },
{ width: 200, height: 200, url: "http://cdn-0.nflximg.com/images/2891/DieHard200.jpg" }
],
"url": "http://api.netflix.com/catalog/titles/movies/70111470",
"rating": 4.0,
"bookmark": []
},
{
"id": 654356453,
"title": "Bad Boys",
"boxarts": [
{ width: 200, height: 200, url: "http://cdn-0.nflximg.com/images/2891/BadBoys200.jpg" },
{ width: 150, height: 200, url: "http://cdn-0.nflximg.com/images/2891/BadBoys150.jpg" }
],
"url": "http://api.netflix.com/catalog/titles/movies/70111470",
"rating": 5.0,
"bookmark": [{ id: 432534, time: 65876586 }]
}
]
}
// ...

因要針對每一筆videos做檢索,所以先

1
2
3
4
5
movieLists.map(obj =>
obj.videos.map(video =>
// ...
)
)

到這一部已經可以拿到id,title了,接著要過濾width !== 150的boxarts

1
2
3
4
5
6
movieLists.map(obj =>
obj.videos.map(video =>
video.boxarts.filter(image => image.width === 150)
// ...
)
)

最後直接回傳我們要的Object形式

1
2
3
4
5
6
7
movieLists.map(obj =>
obj.videos.map(video =>
video.boxarts
.filter(image => image.width === 150)
.map(boxart => { return {id:video.id, title:video.title, boxart:boxart.url}})
)
)

到此階段回傳的Array會多了兩層

1
[[[{"boxart": '...', "id": 70111470, "title": "Die Hard"}], ...]]]

可以在外層兩個map各補上降維的Method

1
2
3
4
5
6
7
movieLists.map(obj =>
obj.videos.map(video =>
video.boxarts
.filter(image => image.width === 150)
.map(boxart => { return {id:video.id, title:video.title, boxart:boxart.url}})
).concatAll()
).concatAll()

雖然這是個簡單的FP範例,但可以感受出每個步驟都具有語意化

Retrieve url of the largest boxart

再來也是要介紹FP核心之一的reduce
data結構如下,要做比較的動作,找出Size最大的圖片(width * height)

1
2
3
4
5
6
var boxarts = [
{ width: 200, height: 200, url: "http://cdn-0.nflximg.com/images/2891/Fracture200.jpg" },
{ width: 150, height: 200, url: "http://cdn-0.nflximg.com/images/2891/Fracture150.jpg" },
{ width: 300, height: 200, url: "http://cdn-0.nflximg.com/images/2891/Fracture300.jpg" },
{ width: 425, height: 150, url: "http://cdn-0.nflximg.com/images/2891/Fracture425.jpg" }
]

reduce第一個參數要傳入處理動作的Function,第二個參數是初始值,預設為Array中第一個元素,所以我們不用特別傳入

1
2
3
4
5
boxarts.reduce((currentImg, current) =>
(currentImg.width * currentImg.height > current)
? currentImg.width * currentImg.height > current
: current)
.map(item => item.url)

以上為簡單的「比較」邏輯,reduce第一個參數中的Function會帶有兩個參數,第一個為當前的元素,第二個為當前的值
因reduce會回傳一個Array,而在這裡我們的Array中只會有一個元素,即Size最大的boxart
map同樣會回傳一個Array,裡面也只有一個url字串

Completing sequences with takeUntil()

takeUntil() 我覺得是個很好用的功能,可以預先註冊好什麼時候停止取資料

1
2
3
4
const stopButtonClicks = Observable.fromEvent(stopButton,'click')
microsoftPrices = pricesNASDAQ
.filter(priceRecord => priceRecord.name === "MSFT")
.takeUntil(stopButtonClicks)

我們假設pricesNASDAQ是一個NASDAQ股市報價的stream,而我們想從中取得微軟的報價所以利用了filter
並註冊了當stopButton按下時就停止取stream資料,可以在這JS Bin試試
會發現當stopButton按下後,會觸發Observer中的complete,其餘還有takeLasttakeWhile可以試試

Throttle Input

接下來要介紹Rx’Library中非常實用的功能之一 - throttleTime
我們可以設定一段時間,例如throttleTime(1000),表示一秒內所有的數據,我們只截取第一筆
這剛好可以解決我們針對某些API或DOM Event要避免短時間內重複觸發的問題,最典型的就是搜尋框(search box)

1
2
3
const input = Rx.Observable
.fromEvent(document.getElementById('input'),'keydown')
.throttleTime(1000)

與之相對的是debounceTime,可以在JS Bin試試

Distinct Until Changed Input

這是最後一個要介紹的Method,只取和前一個相比有變化的值,這概念是否有點熟悉?
在React中我們也是希望Component要有改變到state才render,故有了shouldcomponentupdate這生命週期
那如果有方法能幫我們確保拿到的一定是有變化的數據,是否就能少寫這shouldcomponentupdate了呢
一樣附上JS Bin給大家試試

結語

Rx’Library是個功能很實用的工具,也因為功能很多,所以需要花時間去摸索,當碰到問題時才會發現可以用幾個Method輕鬆的解決
Rx實做多了,也會發現自己Function Programming精進了不少

0%