Ramda 實戰案例若干
免責申明
如果你所在團隊都不熟悉函式式寫法,貿然在專案中寫我即將展示的程式碼,可能會被罵。但如果你團隊都是老司機,或者你自己感興趣,學下無妨。
繼續閱讀之前推薦先看我之前釋出的文章ofollow,noindex">優雅程式碼指北 -- 巧用 Ramda ,這篇文章介紹了 Ramda 的設計理念,解釋了 point free 程式碼風格。瞭解了 point free 和函式組合的優勢,再來看現在這篇文章,會好理解很多。Ramda 的 API 太多了,本文也用了很多,一個個解釋是一項不可能的任務,建議去官網查用法。
你可以在 Ramda 官網的 REPL編輯器裡執行本文的程式碼。直接複製貼上就行了,不用import R from "ramda"
.
首先來個簡單的任務熱下身。
任務一:
把物件中值為空的鍵移除掉。只用判斷一層。
答案:
import R from "ramda"; // 下面就省略匯入這一步了 const clearObj = R.compose( R.fromPairs, R.reject( R.compose( R.isEmpty, R.last ) ), R.toPairs ); const obj3 = { a: {}, b: "x", c: [], d: 9, h: "", x: 0 }; clearObj(obj3); // => {b: "x", d: 9, x: 0} 複製程式碼
注意,R.isEmpty
只判斷陣列物件和字串,若想判斷其它資料型別,可以自己寫判斷函式。
下面做一些複雜的資料操作。
任務二:
有這樣一個狀態庫:
const state = { groupedProducts: { fruits: [ { id: 11, name: "apples", price: 3 }, { id: 12, name: "oranges", price: 4 }, { id: 17, name: "pearls", price: 5 } ], shoes: [ { id: 19, name: "Adidas", price: 11 }, { id: 21, name: "Nike", price: 13 }, { id: 18, name: "Timberland", price: 10 }, { id: 25, name: "New Balance", price: 14 } ], vegetables: [ { id: 31, name: "broccoli", price: 3 }, { id: 32, name: "cabbage", price: 8 }, { id: 33, name: "carrots", price: 4 }, { id: 34, name: "cucumbers", price: 2 } ] }, selectedId: 32, selectedTag: "vegetables", itemsFaved: {} }; 複製程式碼
要求根據選中的id
(selectedId) ,找出當前選中項,並拼接 name 和 price 屬性,用於展示到瀏覽器 header 上。
答案:
const padStart = str => ` -- ${str}`; const findProducts = R.converge(R.call, [ R.compose( R.find, R.propEq("id"), R.prop("selectedId") ), R.converge(R.prop, [R.prop("selectedTag"), R.prop("groupedProducts")]) ]); const getHeaderTag = R.compose( R.converge(R.concat, [ R.compose( R.toUpper, R.propOr("", "name") ), R.compose( padStart, R.toString, R.propOr("", "price") ) ]), findProducts ); const headerTag = getHeaderTag(state); // => CABBAGE -- 8 複製程式碼
任務三:
給定使用者收藏列表如下:
const favList = { fruits: [11], shoes: [19, 21], vegetables: [33, 34] }; 複製程式碼
該列表在每個商品類目下記錄了使用者收藏商品的 id,存在數組裡。
要求在原來groupedProducts
資料裡,在已收藏的商品條目里加上faved : true
資料。比如 Apple 的id: 11
,在收藏列表裡面,修改後應為{ id: 11, name: "apples", price: 3, faved: true }
答案:
const applyFav = list => R.ifElse( R.compose( R.flip(R.contains)(list), R.prop("id") ), R.assoc("faved", true), R.identity ); const applyFavListToProducts = R.mergeWith( (list, target) => R.map(applyFav(list), target), favList ); const addFavToProducts = R.evolve({ groupedProducts: applyFavListToProducts }); addFavToProducts(state); // 結果太長就不寫了,可以自己試 複製程式碼
任務四:
根據上面提供的收藏列表,篩選原資料groupedProducts
,只保留已收藏的商品。
答案:
const getFavedItems = R.mergeWith( R.innerJoin((target, id) => target.id === id), R.__, favList ); const getOnlyFavedItems = R.evolve({ groupedProducts: getFavedItems }); getOnlyFavedItems(state); // 結果太長就不寫了,可以自己試 複製程式碼
任務五:
開發中經常會遇到後端給的資料和 UI 需求不一致的情況,這個時候需要前端對資料進行格式化處理。 給定以下產品列表:
const products = [ { productId: 31, productName: "cars", salesData: [ { brand: "lada", rate: 0.32 }, { brand: "mini", rate: 0.53 }, { brand: "buick", rate: 0.22 } ] }, { productId: 32, productName: "pc", salesData: [ { brand: "lenovo", rate: 0.24 }, { brand: "dell", rate: 0.63 }, { brand: "hp", rate: 0.19 } ] }, { productId: 34, productName: "mobile", salesData: [ { brand: "iphone", rate: 0.78 }, { brand: "sumsung", rate: 0.62 }, { brand: "xiaomi", rate: 0.32 } ] } ]; 複製程式碼
產品資料包含了產品 id,產品名稱和銷售資料。銷售資料裡包含了品牌和價格增長幅度。要求把productId
欄位塞到每個對應銷售記錄裡面,然後把價格漲幅格式化為兩個小數點的百分數。最後把銷售資料按產品名稱分類。
格式化後的資料應該是這樣:
{ cars:[ { brand: "lada", rate: "32.00%", productId: 31 }, // ... ], pc:[ { brand: "lenovo", rate: "24.00%", productId: 32 }, // ... ], mobile:[ { brand: "iphone", rate: "78.00%", productId: 34 }, // ... ] } 複製程式碼
答案:
const toPercentage = num => (num * 100).toFixed(2) + "%"; const getSalesAndFormatRate = R.converge( (salesData, id) => R.map(R.merge(id), salesData), [ R.compose( R.map(R.evolve({ rate: toPercentage })), R.prop("salesData") ), R.pick(["productId"]) ] ); const normalizeProductData = R.converge(R.zipObj, [ R.map(R.prop("productName")), R.map(getSalesAndFormatRate) ]); normalizeProductData(products); 複製程式碼