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 只有 idlanguagecountryId ,但需求是能根據 countryLut 對照表,新增 country property。

Imperative 會使用 for loop,先建立好要回傳的新 array: result ,將新的 object 一一 push()result ,原本的 property 全數使用 ... spread operator 加以保留。

至於查詢 countryLut ,可使用 ECMAScript 的語言特性,直接使用 [] 加以查詢。

這是大家熟悉的寫法,但有幾個問題:

  1. result array 並不是必須的,算是為了結果所產生的 中繼變數
  2. 必須使用 push() 這種 side effect 寫法對 array 新增資料
  3. 必須跟著 for loop 內的程式碼一行一行看下去,才知道到底想做什麼,並不能很直覺的看出這段程式碼的意圖,也就是 Imperative 寫法會嘗試將演算法直接寫在 function 內

Ramda 之如何將 Id 轉換成 Value ?

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 寫法有幾個特色:

  1. 中繼變數 result 不見了
  2. 不必使用 push() 這種 side effect 寫法
  3. map() 意圖明顯,一看到 map() 就知道他要產生新格式 array

但若嚴格來說,還有改善空間:

  1. getCountries() 的參數 data 是否真的需要 ? 能否再簡化 ?
  2. map() 的 callback 參數 x 參數是否真的需要 ? 能否再簡化 ?

Ramda 之如何將 Id 轉換成 Value ?

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 加以簡化,可使用 Ramdamap() 取代 Array.prototype.map() ,callback 則完全保留。

map() callback 的參數 x 依然存在,所以還有重構空間。

Ramda 之如何將 Id 轉換成 Value ?

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。

若以鐵道圖表示如下:

Ramda 之如何將 Id 轉換成 Value ?

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() 執行。

Ramda 之如何將 Id 轉換成 Value ?

對於 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 可供日後重複使用。

Ramda 之如何將 Id 轉換成 Value ?

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)

Ramda 之如何將 Id 轉換成 Value ?

chain() 會自動將參數 x object 直接傳給 assoc() ,我們就不必再使用 identity() 了。

Ramda 之如何將 Id 轉換成 Value ?

Conclusion

  • 由於 Ramda 的參數都是 function,會無形中要求自己重構出很多小 function,而這些小 function 就是日後組合的本錢,會大幅增加開發效率
  • converge() 為實務上常用的 operator,尤其當 operator 間的參數個數不相同時,會需要 converge() 作為中間人加以處理
  • 若一開始覺得 chain() 太玄,無法一步到位想到 chain() ,可先重構成 converge() ,看到 identity 之後,自然會想到 chain()

Reference

Ramda , map()

Ramda , converge()

Ramda , chain()

Ramda , assoc()

Ramda , identity()

Ramda , prop()

Ramda , compose()


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

释放潜能:平台型组织的进化路线图

释放潜能:平台型组织的进化路线图

穆胜 / 人民邮电出版社 / 2017-12 / 59.80元

传统的组织模式中,企业逃不出“员工动不起来”和“创新乏力”的宿命。互联网改变商业逻辑的同时也改变了组织逻辑。平台型组织是匹配互联网商业逻辑的组织模式,它赋予了基层员工更多的责权利,能够在需求侧灵敏获取用户刚需、在供给侧灵活整合各类资源、用“分好钱”的机制激活个体去整合各类资源满足用户刚需,形 成供需之间的高效连接。 打造平台型组织有两大主题:一是通过设计精巧的激励机制让每个人都能感受到市场的压力,......一起来看看 《释放潜能:平台型组织的进化路线图》 这本书的介绍吧!

HTML 压缩/解压工具
HTML 压缩/解压工具

在线压缩/解压 HTML 代码

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具

HEX CMYK 转换工具
HEX CMYK 转换工具

HEX CMYK 互转工具