Ramda 之如何將 Id 轉換成 Value ?
栏目: JavaScript · 发布时间: 5年前
内容简介:實務上從 API 獲得的資料,可能只有 id,並沒有實際的 value,我們必須自行VS Code 1.30.2Quokka 1.0.136
實務上從 API 獲得的資料,可能只有 id,並沒有實際的 value,我們必須自行 map()
將 id 轉換成 value,這個常見的應用,若使用 Ramda 該怎麼寫呢 ?
Version
VS Code 1.30.2
Quokka 1.0.136
Ramda 0.26.1
Imperative
const countryLut = { 1: 'USA', 2: 'Chain', 3: 'Japan', }; const data = [ { id: 1, language: 'English' , countryId: 1}, { id: 2, language: 'Chinese', countryId: 2 }, { id: 3, language: 'Japanese', countryId: 3}, ]; const getData = data => { let result = []; for(let x of data) { result.push({ ...x, country: countryLut[x.countryId], }); } return result; }; console.dir(getData(data));
data
只有 id
、 language
與 countryId
,但需求是能根據 countryLut
對照表,新增 country
property。
Imperative 會使用 for
loop,先建立好要回傳的新 array: result
,將新的 object 一一 push()
進 result
,原本的 property 全數使用 ...
spread operator 加以保留。
至於查詢 countryLut
,可使用 ECMAScript 的語言特性,直接使用 []
加以查詢。
這是大家熟悉的寫法,但有幾個問題:
-
result
array 並不是必須的,算是為了結果所產生的中繼變數
- 必須使用
push()
這種 side effect 寫法對 array 新增資料 - 必須跟著
for
loop 內的程式碼一行一行看下去,才知道到底想做什麼,並不能很直覺的看出這段程式碼的意圖,也就是 Imperative 寫法會嘗試將演算法直接寫在 function 內
Array.prototype
const countryLut = { 1: 'USA', 2: 'Chain', 3: 'Japan', }; const data = [ { id: 1, language: 'English' , countryId: 1}, { id: 2, language: 'Chinese', countryId: 2 }, { id: 3, language: 'Japanese', countryId: 3}, ]; const getCountries = data => data.map(x => ({ ...x, country: countryLut[x.countryId] })); console.dir(getCountries(data));
ECMAScript 提供了 Functional 的 map()
Higer Order Function,讓我們一眼就可以看到他的意圖:就是為了產生一個筆數相同的 array,只是內容略有調整。
map()
的參數為 callback function,其中 x
為 array 中的 object,相當於 for(let x of data)
的 x
。
為了保留 object 原本的 property,一樣使用 ...
spread operator 將 property 展開。
Callback 主要目的就是回傳新 object,因此可使用 {}
直接組新 object,但 ES2015 規定,若直接回傳 object,必須再加上 ()
,否則會誤判。
我們可以發現 Functional 寫法有幾個特色:
- 中繼變數
result
不見了 - 不必使用
push()
這種 side effect 寫法 -
map()
意圖明顯,一看到map()
就知道他要產生新格式 array
但若嚴格來說,還有改善空間:
-
getCountries()
的參數data
是否真的需要 ? 能否再簡化 ? -
map()
的 callback 參數x
參數是否真的需要 ? 能否再簡化 ?
Ramda
Map()
import { map } from 'ramda'; const countryLut = { 1: 'USA', 2: 'Chain', 3: 'Japan', }; const data = [ { id: 1, language: 'English' , countryId: 1}, { id: 2, language: 'Chinese', countryId: 2 }, { id: 3, language: 'Japanese', countryId: 3}, ]; const getCountries = map(x => ({ ...x, country: countryLut[x.countryId] })); console.dir(getCountries(data));
我們希望將 getCountries()
的參數 data
加以簡化,可使用 Ramda
的 map()
取代 Array.prototype.map()
,callback 則完全保留。
但 map()
callback 的參數 x
依然存在,所以還有重構空間。
Converge()
import { map, assoc, converge, identity, compose, prop, __ } from 'ramda'; const countryLut = { 1: 'USA', 2: 'Chain', 3: 'Japan', }; const data = [ { id: 1, language: 'English' , countryId: 1}, { id: 2, language: 'Chinese', countryId: 2 }, { id: 3, language: 'Japanese', countryId: 3}, ]; const lookupLut = (propName, lut) => compose( prop(__, lut), prop(propName), ); const getCountries = map(converge( assoc('country'), [ lookupLut('countryId', countryLut), identity, ] )); console.dir(getCountries(data));
回想 map()
的 callback 部分:
x => ({ ...x, country: countryLut[x.countryId] })
說穿了我們的目的就是:從 object 取得 countryId
值,並對 countryLut
查表,最後將其值新增至 country
property。
若以鐵道圖表示如下:
15 行
const lookupLut = (propName, lut) => compose( prop(__, lut), prop(propName), );
其中前兩個流程 從 object 取得 countryId 值
與 對 countryLut 查表
,這兩個流程在實務上經常遇到,可以抽出來寫成 function:傳入 property 名稱與 LUT object
透過 Ramda 的 compose()
,可將兩個 prop()
加以組合成新 function。
像 lookupLut()
這類 general function,可以重構到 helper module 內,將來其他地方要用只要 import lookupLut()
即可使用,FP 就是因為這些小 function 存在,所以重複使用的程度非常高。
20 行
const getCountries = map(converge( assoc('country'), [ lookupLut('countryId', countryLut), identity, ] )); console.dir(getCountries(data));
前兩個流程都解決了,剩下最後一個流程: 將其值新增至 country
,Ramda 特提供了 assoc()
負責對 object 新增 property。
assoc()
String -> a -> {k: v} -> {k: v}
複製原本 object 的 property 外,還可新增 property
assoc()
的第一個參數是新的 property 名稱,所以我們傳入 country
。
第二個參數要傳入的是其 value,我們已經建立了 lookupLut()
,這也沒問題。
第三個參數要傳入為原 object,也就是 map()
callback 的 x
。
但卻面臨了一個問題:
assoc()
該如何與 map()
串起來 ? map()
的 callback 只有一個參數 x
object,但 assoc()
要的卻是兩個參數:value 與 object。
這是 Ramda 初學者一定會卡關的地方。
Ramda 另外提供了 converge()
,負責將原本一個參數變成兩個參數,再交給 assoc()
執行。
對於 lookupLut('countryId', countryLut)
而言,因為 lookupLut()
的兩個參數已經填滿,其實 x
object 傳進去會忽略,不過並不會影響結果。
assoc()
第三個參數要的就是 x
object,所以使用了 Ramda 的 identity
,傳回自己本身的 object,在 FP 稱為 Identity Function。
identity()
a -> a
產生回傳與原本輸入值相同的 function
透過 converge()
, map()
與 assoc()
就接起來了。
我們可以發現 map()
callback 的 x
被 Point-free 掉了,且還產生了 lookupLut()
Higher Order Function 可供日後重複使用。
Chain()
import { map, assoc, chain, compose, prop, __ } from "ramda"; const countryLut = { 1: "USA", 2: "Chain", 3: "Japan" }; const data = [ { id: 1, language: "English", countryId: 1 }, { id: 2, language: "Chinese", countryId: 2 }, { id: 3, language: "Japanese", countryId: 3 } ]; const lookupLut = (propName, lut) => compose( prop(__, lut), prop(propName) ); const getCountries = map(chain( assoc("country"), lookupLut("countryId", countryLut)) ); console.dir(getCountries(data));
使用 converge()
已經接近完美,配合 identity()
可讀性也高,但 identity()
似乎是一個可有可無的 function,能否也 Point-free 掉呢 ?
20 行
const getCountries = map(chain( assoc("country"), lookupLut("countryId", countryLut)) );
使用 Ramda 的 chain()
可將 identity()
省略。
chain()
Chain m => (a -> m b) -> m a -> m b
chain(f, g)(x) === f(g(x), x)
chain()
會自動將參數 x
object 直接傳給 assoc()
,我們就不必再使用 identity()
了。
Conclusion
- 由於 Ramda 的參數都是 function,會無形中要求自己重構出很多小 function,而這些小 function 就是日後組合的本錢,會大幅增加開發效率
-
converge()
為實務上常用的 operator,尤其當 operator 間的參數個數不相同時,會需要converge()
作為中間人加以處理 - 若一開始覺得
chain()
太玄,無法一步到位想到chain()
,可先重構成converge()
,看到identity
之後,自然會想到chain()
Reference
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
释放潜能:平台型组织的进化路线图
穆胜 / 人民邮电出版社 / 2017-12 / 59.80元
传统的组织模式中,企业逃不出“员工动不起来”和“创新乏力”的宿命。互联网改变商业逻辑的同时也改变了组织逻辑。平台型组织是匹配互联网商业逻辑的组织模式,它赋予了基层员工更多的责权利,能够在需求侧灵敏获取用户刚需、在供给侧灵活整合各类资源、用“分好钱”的机制激活个体去整合各类资源满足用户刚需,形 成供需之间的高效连接。 打造平台型组织有两大主题:一是通过设计精巧的激励机制让每个人都能感受到市场的压力,......一起来看看 《释放潜能:平台型组织的进化路线图》 这本书的介绍吧!