Ramda 实战案例若干

你可以在 Ramda 官网的 REPL 编辑器里运行本文的代码。直接复制粘贴就行了,不用 import R from "ramda".

首先来个简单的任务热下身。

任务一:

把对象中值为空的键移除掉。只用判断一层。

答案:

import R from 'ramda'; // 下面就省略导入这一步了

const clearObj = R.reject(R.isEmpty);

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);