實務上 API 常常只回傳 ID,前端必須根據 ID 查出所對應的 String 顯示,這常見需求該如何使用 Ramda 實現呢 ?
Imperative
let data = [
{ title: 'FP in JavaScript', price: 100, categoryId: 0 },
{ title: 'Rx in Action', price: 200, categoryId: 1 },
{ title: 'Speaking JavaScript', price: 300, categoryId: 2 }
]
let categoryMap = {
0: 'FP',
1: 'FRP',
2: 'JS'
}
let f = obj => arr => {
let result = []
for(let x of arr) {
let x_ = Object.assign({}, x)
x_['category'] = obj[x.categoryId]
result.push(x_)
}
return result
}
console.dir(f(categoryMap)(data))
第 1 行
let data = [
{ title: 'FP in JavaScript', price: 100, categoryId: 0 },
{ title: 'Rx in Action', price: 200, categoryId: 1 },
{ title: 'Speaking JavaScript', price: 300, categoryId: 2 }
]
data
只有 categoryId
。
第 7 行
let categoryMap = {
0: 'FP',
1: 'FRP',
2: 'JS'
}
需自行根據 categoryMap
object 所對應的 value 顯示。
13 行
let f = obj => arr => {
let result = []
for(let x of arr) {
let x_ = Object.assign({}, x)
x_['category'] = obj[x.categoryId]
result.push(x_)
}
return result
}
f()
需傳入 obj
與 arr
,其中 obj
為查詢 id 與對應 value 的 object,arr
為原始 data。
Imperative 會先建立 result
empty array,使用 for
loop 對 data 一筆一筆處理,然後使用 Object.assign()
clone object,對新 object 新增 category
property,其 value 使用 categoryId
對照而來,最後 push 進 result
array 回傳。
ECMAScript
reduce()
let data = [
{ title: 'FP in JavaScript', price: 100, categoryId: 0 },
{ title: 'Rx in Action', price: 200, categoryId: 1 },
{ title: 'Speaking JavaScript', price: 300, categoryId: 2 }
]
let categoryMap = {
0: 'FP',
1: 'FRP',
2: 'JS'
}
let f = obj => arr => arr.reduce((a, x) => [...a, {...x, category: obj[x.categoryId]}], [])
console.dir(f(categoryMap)(data))
只要能使用 for
loop,就能使用 reduce()
改寫。
map()
let data = [
{ title: 'FP in JavaScript', price: 100, categoryId: 0 },
{ title: 'Rx in Action', price: 200, categoryId: 1 },
{ title: 'Speaking JavaScript', price: 300, categoryId: 2 }
]
let categoryMap = {
0: 'FP',
1: 'FRP',
2: 'JS'
}
let f = obj => arr => arr.map(x => ({
...x,
category: obj[x.categoryId]
}))
console.dir(f(categoryMap)(data))
ECMAScript 可使用其 Array.prototype.map()
,並搭配 ...
object spread 展開 object 建立新 object。
Ramda
import { map } from 'ramda'
let data = [
{ title: 'FP in JavaScript', price: 100, categoryId: 0 },
{ title: 'Rx in Action', price: 200, categoryId: 1 },
{ title: 'Speaking JavaScript', price: 300, categoryId: 2 }
]
let categoryMap = {
0: 'FP',
1: 'FRP',
2: 'JS'
}
let f = obj => map(x => ({
...x,
category: obj[x.categoryId]
}))
console.dir(f(categoryMap)(data))
可使用 Ramda 的 map 將 arr
argument point-free。
assoc()
import { map, assoc, prop } from 'ramda'
let data = [
{ title: 'FP in JavaScript', price: 100, categoryId: 0 },
{ title: 'Rx in Action', price: 200, categoryId: 1 },
{ title: 'Speaking JavaScript', price: 300, categoryId: 2 }
]
let categoryMap = {
0: 'FP',
1: 'FRP',
2: 'JS'
}
let f = obj => map(x => assoc('category')(obj[prop('categoryId')(x)])(x))
console.dir(f(categoryMap)(data))
對於新增 property,Ramda 提供了 assoc()
,如此可不再使用 ...
object spread 展開 object。
chain()
import { map, assoc, prop, chain, compose, flip } from 'ramda'
let data = [
{ title: 'FP in JavaScript', price: 100, categoryId: 0 },
{ title: 'Rx in Action', price: 200, categoryId: 1 },
{ title: 'Speaking JavaScript', price: 300, categoryId: 2 }
]
let categoryMap = {
0: 'FP',
1: 'FRP',
2: 'JS'
}
let f = obj => map(
chain(assoc('category'), compose(flip(prop)(obj), prop('categoryId')))
)
console.dir(f(categoryMap)(data))
assoc()
常與其他 function 透過 chain()
組合,之後透過 compose()
組合 prop()
與 flip()
從 object 查出 value。
prop_()
import { map, assoc, prop, chain, compose, flip } from 'ramda'
let data = [
{ title: 'FP in JavaScript', price: 100, categoryId: 0 },
{ title: 'Rx in Action', price: 200, categoryId: 1 },
{ title: 'Speaking JavaScript', price: 300, categoryId: 2 }
]
let categoryMap = {
0: 'FP',
1: 'FRP',
2: 'JS'
}
let prop_ = flip(prop)
let f = obj => map(
chain(assoc('category'), compose(prop_(obj), prop('categoryId')))
)
console.dir(f(categoryMap)(data))
chain()
後面一堆 function 可讀性不高,由於 curried function 特性,可任意抽出 function。
可先將 flip(prop)
抽成 prop_()
。
consult()
import { map, assoc, prop, chain, compose, flip } from 'ramda'
let data = [
{ title: 'FP in JavaScript', price: 100, categoryId: 0 },
{ title: 'Rx in Action', price: 200, categoryId: 1 },
{ title: 'Speaking JavaScript', price: 300, categoryId: 2 }
]
let categoryMap = {
0: 'FP',
1: 'FRP',
2: 'JS'
}
let prop_ = flip(prop)
let consult = obj => compose(prop_(obj), prop('categoryId'))
let f = obj => map(
chain(assoc('category'), consult(obj))
)
console.dir(f(categoryMap)(data))
第 7 行
let consult = obj => compose(prop_(obj), prop('categoryId'))
compose()
一般也避免放在 callback 內,建議抽成獨立 function 增加可讀性。
19 行
let f = obj => map(
chain(assoc('category'), consult(obj))
)
如此就可一目瞭然看出由 assoc()
與 consult()
透過 chain()
組合出 callback。
Conclusion
chain()
屬於 Ramda 較高難度應用,但因為assoc()
常與chain()
搭配,只要看到assoc()
就要想到chain()
- 凡使用
flip()
或compose()
都建議抽成獨立 function 增加可讀性
Reference
Ramda, assoc()
Ramda, chain()
Ramda, map()
Ramda, prop()
Ramda, flip()
Ramda, compose()