免责申明
如果你所在团队都不熟悉函数式写法,贸然在项目中写我即将展示的代码,可能会被骂。但如果你团队都是老司机,或者你自己感兴趣,学下无妨。
继续阅读之前推荐先看我之前发布的文章优雅代码指北 -- 巧用 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);
复制代码