工作总结:两种处理数据的思维
五秒导读
一句总结:跳出思维的"墙",处理数据从'单个元素'到'列表处理'
- 列表处理(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;
});
最后要说明一点这个类比和实际代码的一个区别,那就是 map
和 filter
都是 immutable methods
,也就是说它们只会返回一个新数组,而不会改变原来的那个数组,所以这里 filter
的例子是和代码有些出入的(原来的盒子里的钱包减少了),但为了形象说明,大家理解就好。
这里还有一个知乎作者的图解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 Component) 是 React 0.14 之后推出的,大大增强了编写 React 组件的方便性,也提升了整体的渲染性能。
需注意的是:
- 参数使用
props
关键字- 无状态组件不支持 "ref"
- 无状态组件尚不支持 babel-plugin-react-transform 的 Hot Module Replacement
这里是相关文章:
React.js学习笔记之组件属性与状态
无状态组件(Stateless Component) 与高阶组件
React/React Native 的ES5 ES6写法对照表