跳出单个元素的思维

工作总结:两种处理数据的思维

五秒导读

一句总结:跳出思维的"墙",处理数据从'单个元素'到'列表处理'

  • 列表处理(pipeline)看成一个整体,而不是把每个步骤看成循环
  • filter/map/reduce
  • 跳出单个元素的思维(跟for说再见)
  • 项目中的传参

Pipeline

所谓**“管道”**,指的是通过a.pipe(b)的形式连接起来的多个Stream对象的组合。

假如现在有两个Transform:bold和red,分别可将文本流中某些关键字加粗和飘红。
可以按下面的方式对文本同时加粗和飘红:

// source: 输入流
// dest: 输出目的地
source.pipe(bold).pipe(red).pipe(dest)

bold.pipe(red)便可以看作一个管道,输入流先后经过bold和red的变换再输出。

但如果这种加粗且飘红的功能的应用场景很广,我们期望的使用方式是:

// source: 输入流
// dest: 输出目的地
// pipeline: 加粗且飘红
source.pipe(pipeline).pipe(dest)

filter/map/reduce and forEach

下文来自知乎,作者:尤雨溪

假设我们有一个数组,每个元素是一个人。你面前站了一排人。
foreach 就是你按顺序一个一个跟他们做点什么,具体做什么,随便:

people.forEach(function (dude) {
  dude.pickUpSoap();
});

map 就是你手里拿一个盒子(一个新的数组),一个一个叫他们把钱包扔进去。结束的时候你获得了一个新的数组,里面是大家的钱包,钱包的顺序和人的顺序一一对应。

var wallets = people.map(function (dude) {
  return dude.wallet;
});

reduce 就是你拿着钱包,一个一个数过去看里面有多少钱啊?每检查一个,你就和前面的总和加一起来。这样结束的时候你就知道大家总共有多少钱了。

var totalMoney = wallets.reduce(function (countedMoney, wallet) {
  return countedMoney + wallet.money;
}, 0);

filter:你一个个钱包数过去的时候,里面钱少于 100 块的不要(留在原来的盒子里),多于 100 块的丢到一个新的盒子里。这样结束的时候你又有了一个新的数组,里面是所有钱多于 100 块的钱包:

var fatWallets = wallets.filter(function (wallet) {
  return wallet.money > 100;
});

最后要说明一点这个类比和实际代码的一个区别,那就是 mapfilter 都是 immutable methods,也就是说它们只会返回一个新数组,而不会改变原来的那个数组,所以这里 filter 的例子是和代码有些出入的(原来的盒子里的钱包减少了),但为了形象说明,大家理解就好。

这里还有一个知乎作者的图解Javascript数组迭代:
作者:水乙
Javascript数组迭代

这还有篇文章写的非常不错,很形象的对比了老语法实现map filter reduce,JavaScript’s Map, Reduce, and Filter

跳出单个元素的思维

先来看一下我的任务:
商品视图
订单视图

container先用mock的数据,稍后再实现数据库访问。
“商品视图”中的未结账数量是从各订单中累加起来的,虽然同一商品在不同订单中价格可能不同,但“商品视图”中只关心总量,不关心价格。
顾客对着商品视图和盒子里的剩余,就知道自己已经吃了多少个。同时“商品视图”中还把预订订单累加起来,显示预订数,以便共用同一个盒子的几个人都能看到,以免大家没商量好重复下了订单。
“订单视图”中显示所有未结账或未完全结账的订单,未完全结账的订中每个商品都可以看到有多少个已经结了账,有多少个还没结,商品在每个订单中的单价是多少,到时候送货员上门也是用这个视图跟顾客核对收款的。

大概的数据是这样的:

[
  {"_id": "order5", "boxId": "AAA", "customer": "张小三", "createAt": "2016-09-01T00:06:00", "items": [
    {"snackId": "aaaa", "price": 6.5, "paid": 0, "unpaid": 10, "reject": 0},
    {"snackId": "cccc", "price": 3, "paid": 0, "unpaid": 3, "reject": 0}
  ]},
  {"_id": "order4", "boxId": "AAA", "customer": "张三", "createAt": "2016-09-01T00:05:00", "items": [
    {"snackId": "aaaa", "price": 6.5, "paid": 0, "unpaid": 5, "reject": 0},
    {"snackId": "dddd", "price": 3, "paid": 0, "unpaid": 3, "reject": 0}
  ]},
  {"_id": "order3", "boxId": "AAA", "customer": "李四", "createAt": "2016-08-21T10:25:00", "deliverAt": "2016-08-21T23:42:00", "items": [
    {"snackId": "aaaa", "price": 6.5, "paid": 2, "unpaid": 3, "reject": 0},
    {"snackId": "cccc", "price": 4.5, "paid": 0, "unpaid": 2, "reject": 0}
  ]},
  {"_id": "order2", "boxId": "BBB", "customer": "王五", "createAt": "2016-08-16T14:09:00", "deliverAt": "2016-08-17T03:30:00", "items": [
    {"snackId": "bbbb", "price": 3, "paid": 7, "unpaid": 0, "reject": 0},
    {"snackId": "cccc", "price": 4.5, "paid": 7, "unpaid": 0, "reject": 0}
  ]},
  {"_id": "order1", "boxId": "AAA", "customer": "张三", "createAt": "2016-08-03T06:17:00", "deliverAt": "2016-08-03T12:00:00", "items": [
    {"snackId": "cccc", "price": 1, "paid": 9, "unpaid": 1, "reject": 2}
  ]}
]

我的理解:
合并两个新订单的items,然后再把相同的累加起来,再对比最新的一个带有deliverAt字段的订单,把新的商品抽取出来,这就是这次新添加的商品,剩下的就是在原来的基础上补货了.

我的绊脚石一:处理多个订单,然后把符合要求的订单的items单独抽出来.
解决:
使用了笨办法,把items拿出来放进一个新的数组;

let temp = [];
  const newItems = Orders.filter(order => _.includes(order, snackboxId) && !order.deliverAt).map(item => (item.items));
  newItems.forEach(function (order) {
    order.forEach(function (i) {
      temp.push(i);
    });
  });

我的绊脚石二:拿到了所有的items,但是面临多个相同的items不知道如何合并相同名称的商品,并且将数据累加起来
我拿到的数据大概是这样的:

[
  [
    {
      paid:0
      price:6.5
      reject:0
      snackId:"aaaa"
      Unpaid:10
    },
    {
      paid:0
      price:6.5
      reject:0
      snackId:"aaaa"
      Unpaid:10
    }

  ],
  [
    {
      paid:0
      price:6.5
      reject:0
      snackId:"aaaa"
      Unpaid:10
    },
    {
      paid:0
      price:6.5
      reject:0
      snackId:"aaaa"
      Unpaid:10
    }

  ],
  // ...
]

好了,接下来就是将他们groupBy然后reduce了,思路这样没错,可是我的代码写的那是一个加粗的:

  // groupBy
  let groupItems = _.groupBy(temp, 'snackId');
  // 更新Items的属性
  let updatedItems = _.map(groupItems, function (content, key) {
    let paid = [];
    let unpaid = [];
    content.forEach(function (res) {
      paid.push(res.paid);
      unpaid.push(res.unpaid);
    });
    let result = {};
    result.snackId = key;
    result.reserve = unpaid.reduce((pre, cur) => (pre + cur));
    result.paid = paid.reduce((pre, cur) => (pre + cur));
    return result;
  });

  //合并订单与存货,添加商品信息,我想让两个数组通过循环的方式去对比,这里也是没有摆脱单个思维元素的地方
  updatedItems.forEach(function (item) {
    let SWITCH;
    for (let i = 0; i < lastStock.length; i++){
      if (lastStock[i].snackId === item.snackId) {
        lastStock[i].reserve = item.reserve;
        lastStock[i].name = Snacks.find(snack => (snack._id === lastStock[i].snackId)).name;
        SWITCH = false;
        return;
      }
      
      SWITCH = true;
    }
    if (SWITCH) {
      item.new = true;
      item.unpaid = 0;
      item.name = Snacks.find(snack => (snack._id === item.snackId)).name;
      lastStock.push(item);
    }
  });

接下来就是CTO宋劲杉老师的25行吊打代码:

 let items = _(res) // [{..[].}, {..[].}, {..[].}, {..[].}]
        .filter(order => !!order.deliverAt) // [{..[].}, {..[].}, {..[].}]
        .reduce((sum, order) => sum.concat(order.items), []); // [{...}, {...}, {...}]
  items = _(items)
    .groupBy('snackId') // {snackId1: [{...}, {...}], snackId2: [{...}]}
    .mapValues((itemsPerSnackId, snackId) => ({
      snackId,
      name: itemsPerSnackId[0].name,
      unpaid: itemsPerSnackId.reduce((sum, item) => sum + item.unpaid, 0)
    }))
    .value(); // {id1: {snackId: 'id1', name: "XXXX", unpaid: X}, id2: {snackId: 'id2', name: "YYYY", unpaid: Y}}
  let newOrderItems = _(res)
    .filter(order => !order.deliverAt)
    .reduce((sum, order) => sum.concat(order.items), []);
  newOrderItems = _(newOrderItems)
    .groupBy('snackId')
    .mapValues((itemsPerSnackId, snackId) => ({
      snackId,
      name: itemsPerSnackId[0].name,
      ordered: itemsPerSnackId.reduce((sum, item) => sum + item.unpaid, 0)
    }))
    .value();
  _.merge(items, newOrderItems);

的确值得我好好学习一下,特别是Pipeline的这种思维,在我的代码中,最后一部分,还用了flag,自己写的时候都觉得好蠢.

项目中的总结

无状态组件:

这种组件没有状态,没有生命周期,只是简单的接受 props 渲染生成 DOM 结构,无状态组件非常简单,开销很低,如果可能的话尽量使用无状态组件。比如使用箭头函数定义.

// 注意传入参数
const HelloMessage = (props) => <div> Hello {props.name}</div>;
render(<HelloMessage name="John" />, mountNode);

// 项目中被宋老师替换
class MySnackBox extends React.Component {...}

export default (props) => {...}

Stateless Functions

无状态组件(Stateless Component) 是 React 0.14 之后推出的,大大增强了编写 React 组件的方便性,也提升了整体的渲染性能。

需注意的是:

  • 参数使用props关键字
  • 无状态组件不支持 "ref"
  • 无状态组件尚不支持 babel-plugin-react-transform 的 Hot Module Replacement

这里是相关文章:
React.js学习笔记之组件属性与状态
无状态组件(Stateless Component) 与高阶组件
React/React Native 的ES5 ES6写法对照表

Show Comments