雖然 API 會回傳 Observable,但其實骨子裡都還是 ECMAScript 的 Array 與 Object,實務上我們常只需要 Object 中部分 Property 即可,這在 RxJS 該如何做呢 ?
Version
macOS Catalina 10.15.4
WebStorm 2020.1.1
Vue 2.6.11
Vue-rx 6.2
RxJS 6.5.5
Ramda 0.27.0
RxJS
<template>
<div>
</div>
</template>
<script>
import { ajax } from 'rxjs/ajax'
import { pluck } from 'rxjs/operators'
let subscriptions = _ => {
ajax('http://localhost:3000/books').pipe(
pluck('response')
).subscribe(console.log)
}
export default {
name:'App',
subscriptions
}
</script>
11 行
ajax('http://localhost:3000/books').pipe(
pluck('response')
).subscribe(console.log)
讀取 'http://localhost:3000/books'
API,發現其有 id
、title
、price
、categoryId
與 image
眾多 property。
但我們只希望保留 title
、price
即可。
Array.prototype.map()
<template>
<div>
<ul v-for="(x, i) in books$" :key="i">
<li>{{ x.title }} / {{ x.price }}</li>
</ul>
</div>
</template>
<script>
import { ajax } from 'rxjs/ajax'
import { map, pluck } from 'rxjs/operators'
let fetchBooks$ = ajax('http://localhost:3000/books').pipe(
pluck('response'),
)
let subscriptions = _ => {
let books$ = fetchBooks$.pipe(
map(x => x.map(y => ({ title: y.title, price: y.price })))
)
return { books$ }
}
export default {
name:'App',
subscriptions
}
</script>
13 行
let fetchBooks$ = ajax('http://localhost:3000/books').pipe(
pluck('response'),
)
抽出 fetchBooks$
專門從 http://localhost:3000/books
取出資料,回傳為 Observable。
18 行
let books$ = fetchBooks$.pipe(
map(x => x.map(y => ({ title: y.title, price: y.price })))
)
Observable 有 map()
,但這種 map()
與我們習慣的 Array map()
又不太一樣,比較類似 Maybe 的 map()
,專門用來改變 Observable 內部值。
Observable map()
內的 x
事實上為 Array 而非 object,若我們只想取得 object 的部分 property,這無法從 RxJS 中的 operator 得到答案。
但我們可用 Array 自帶的 map()
達成需求。
Ramda
<template>
<div>
<ul v-for="(x, i) in books$" :key="i">
<li>{{ x.title }} / {{ x.price }}</li>
</ul>
</div>
</template>
<script>
import { ajax } from 'rxjs/ajax'
import { map, pluck } from 'rxjs/operators'
import { map as fmap, pick } from 'ramda'
let fetchBooks$ = ajax('http://localhost:3000/books').pipe(
pluck('response'),
)
let subscriptions = _ => {
let books$ = fetchBooks$.pipe(
map(fmap(pick(['title', 'price'])))
)
return { books$ }
}
export default {
name:'App',
subscriptions
}
</script>
map()
的 x
既然是 array,且是 synchronous,這就是 Ramda 的強項了。
12 行
import { map as fmap, pick } from 'ramda'
因為在 Ramda 也稱為 map()
,與 RxJS 同名,只好 alias 成為 fmap()
。
fmap()
命名是取自於 Functor 的fmap()
19 行
let books$ = fetchBooks$.pipe(
map(fmap(pick(['title', 'price'])))
)
透過 fmap()
與 pick()
組合就能使 map()
的 callback 也 point-free 了。
Function Composition
<template>
<div>
<ul v-for="(x, i) in books$" :key="i">
<li>{{ x.title }} / {{ x.price }}</li>
</ul>
</div>
</template>
<script>
import { ajax } from 'rxjs/ajax'
import { map, pluck } from 'rxjs/operators'
import { map as fmap, pick, compose } from 'ramda'
let onlyProps = compose(fmap, pick)
let fetchBooks$ = ajax('http://localhost:3000/books').pipe(
pluck('response'),
)
let subscriptions = _ => {
let books$ = fetchBooks$.pipe(
map(onlyProps(['title', 'price']))
)
return { books$ }
}
export default {
name:'App',
subscriptions
}
</script>
14 行
let onlyProps = compose(fmap, pick)
可將 fmap()
與 pick()
先組合起來。
21 行
let books$ = fetchBooks$.pipe(
map(onlyProps(['title', 'price']))
)
map()
內可直接使用 onlyProps()
,可讀性超高。
也可將
onlyProps()
放在自己的 helper 以供未來使用
Conclusion
- RxJS 與 Ramda 其實各擅勝場,RxJS 強在 asynchronous,而 Ramda 強在 synchronous,尤其 Ramda 提供非常多 Array 與 Object 的 function,很適合在 RxJS 的 callback 中使用