我正在通过this tutorial,它正在使用reduce
来获取Array<Student>
并将其变成{ [key: string]: Array<string | number> }
,其中包含该表达式。这是我第一次看到这样的内容,因此我想检查一下我是否正确。这是整个表达式:
export interface Student {
id: string;
name: string;
sex: 'Male' | 'Female';
standard: number;
quarterlyScore: number;
halfyearlyScore: number;
annualScore: number;
}
export function getStudentGraphData(students:Array<Student>): { [key: string]: Array<string |number> } {
return students.reduce((
{ names: nArray, quarterly: qArray,halfyearly: hArray, annual: aArray },
{ name, quarterlyScore, halfyearlyScore,annualScore }) => {
return {
names: [...nArray, name],
quarterly: [...qArray,quarterlyScore],
halfyearly: [...hArray,halfyearlyScore],
annual: [...aArray, annualScore]
};
}, { names: [], quarterly: [], halfyearly: [, annual: [] });
}
IIUC这是我们要减少到的部分(返回值):
{ names: nArray, quarterly: qArray, halfyearly: hArray, annual: aArray }
这是学生对象:
{ name, quarterlyScore, halfyearlyScore, annualScore }
这是实际的减少量。它使用上一步中的数组,并使用spread(...
)运算符将其分解为新数组,然后将来自学生对象的参数(如name
放在新数组的末尾)
return {
names: [...nArray, name],
quarterly: [...qArray, quarterlyScore],
halfyearly: [...hArray, halfyearlyScore],
annual: [...aArray, annualScore]
};
这是返回值的初始值:
{ names: [], quarterly: [], halfyearly: [], annual: [] }
我大致正确吗?
答案 0 :(得分:2)
我大致正确吗?
是的。每次对reduce
回调的调用都会创建一个新对象,并将原始对象中的数组扩展为新数组,其中每个对象都具有name
(等等)。因此,对于students
中的每个条目,代码都会创建并丢弃五个对象(容器对象和四个数组)。
没有所有这些临时对象的for-of
版本将是:
const names = [];
const quarterly = [];
const halfyearly = [];
const annual = [];
for (const { name, quarterlyScore, halfyearlyScore, annualScore } of students) {
names.push(name);
quarterly.push(quarterlyScore);
halfyearly.push(halfyearlyScore);
annual.push(annualScore);
}
return {names, quarterly, halfyearly, annual};
或者,如果map
数组的时间不长,以至于您要遍历四遍而不是遍历一次,则可以进行四个students
调用(通常不会):
return {
names: students.map(({name}) => name),
quarterly: students.map(({quarterlyScore}) => quarterlyScore),
halfyearly: students.map(({halfyearlyScore}) => halfyearlyScore),
annual: students.map(({annualScore}) => annualScore)
};
答案 1 :(得分:2)
即使您使用reduce
技术,您的解决方案也过于复杂。罪魁祸首是对累加器属性进行重命名,只是在返回新的累加器时才对其重命名。更简单的是只保留累加器名称,尤其是当它们与您的项目属性名称不冲突时。
此版本进行了清理,删除了TS注释(您可能必须添加回去;但它们确实会使事情变得混乱),并用箭头替换外部函数声明:
const getStudentGraphData = (students) => students.reduce(
( { names, quarterly, halfyearly, annual },
{ name, quarterlyScore, halfyearlyScore, annualScore }
) => ({
names: [...names, name],
quarterly: [...quarterly, quarterlyScore],
halfyearly: [...halfyearly, halfyearlyScore],
annual: [...annual, annualScore]
}),
{ names: [], quarterly: [], halfyearly: [], annual: [] }
)
const students = [{id: 1, name: 'Barney', sex: 'Male', quarterlyScore: 83, halfyearlyScore: 88, annualScore: 91}, {id: 2, name: 'Betty', sex: 'Female', quarterlyScore: 92, halfyearlyScore: 89, annualScore: 95}, {id: 3, name: 'Fred', sex: 'Male', quarterlyScore: 69, halfyearlyScore: 73, annualScore: 68}, {id: 4, name: 'Wilma', sex: 'Female', quarterlyScore: 85, halfyearlyScore: 78, annualScore: 80}]
console.log(getStudentGraphData(students))
但是有时候正确的抽象可以自行清理。这是另一个版本,其最终工作方式类似于多个map
版本,但将其抽象为更具声明性的功能:
const collect = (fields) => (objs) => Object.entries(fields).reduce(
(a, [k, v]) => ({...a, [k]: objs.map(o => o[v])}),
{}
)
const getStudentGraphData = collect({
names: 'name',
quarterly: 'quarterlyScore',
halfyearly: 'halfyearlyScore',
annual: 'annualScore',
})
const students = [{id: 1, name: 'Barney', sex: 'Male', quarterlyScore: 83, halfyearlyScore: 88, annualScore: 91}, {id: 2, name: 'Betty', sex: 'Female', quarterlyScore: 92, halfyearlyScore: 89, annualScore: 95}, {id: 3, name: 'Fred', sex: 'Male', quarterlyScore: 69, halfyearlyScore: 73, annualScore: 68}, {id: 4, name: 'Wilma', sex: 'Female', quarterlyScore: 85, halfyearlyScore: 78, annualScore: 80}]
console.log(getStudentGraphData(students))
collect
函数可能在代码库中的其他地方很有用,但是即使不是,getStudentGraphData
现在更具声明性的版本也可能值得添加collect
。
我在创建collect
时遇到的唯一API问题是如何确定目标名称(names
,quarterly
等)是否应为键和源名称({ {1}},name
等),或者相反。感觉稍微正确一些,但是两种版本都使quarterlyScore
更加容易理解。
由于某种原因,它一直卡在我的脑海中。我一直在思考像getStudentGraphData
这样的API应该具有什么样的API。虽然我对最后一个版本感到非常满意,但这是一个截然不同的版本,并且它不会混淆正在收集的内容以及这些结果称为什么:
collect
这是一种有点不寻常的技术,我只在生产代码中使用过几次,但是效果很好,const collect = field => {
let fields = [[field, field]]
const fn = (objs) => fields.reduce(
(a, [k, v]) => ({...a, [k]: objs.map(o => o[v])}),
{}
)
fn.and = (field) => {
fields.push([field, field])
return fn
}
fn.as = (field) => {
fields[fields.length - 1][0] = field
return fn;
}
return fn;
}
const getStudentGraphData = collect('id')
.and('name')
.and('quarterlyScore').as('quarterly')
.and('halfyearlyScore').as('halfyearly')
.and('annualScore').as('annual')
const students = [{id: 1, name: 'Barney', sex: 'Male', quarterlyScore: 83, halfyearlyScore: 88, annualScore: 91}, {id: 2, name: 'Betty', sex: 'Female', quarterlyScore: 92, halfyearlyScore: 89, annualScore: 95}, {id: 3, name: 'Fred', sex: 'Male', quarterlyScore: 69, halfyearlyScore: 73, annualScore: 68}, {id: 4, name: 'Wilma', sex: 'Female', quarterlyScore: 85, halfyearlyScore: 78, annualScore: 80}]
console.log(getStudentGraphData(students))
的定义几乎可以想象得到。
我对getStudentGraphData
的看法是它功能强大且必要,但如果可以使用reduce
,map
,filter
,{{ 1}},find
等。这些可以清楚地说明您在代码中的工作。 some
更像是every
循环:通常不言自明。
答案 2 :(得分:1)
看到更高效,更优雅的答案很酷。
我猜想,一种更有效(不确定“优雅”的方法)的方法是制作一个 generic 函数,该函数抽象手头的计算并且不将其自身局限于特定的数据结构。例如:
let collect = objects => {
let res = {};
for (let obj of objects)
for (let [k, v] of Object.entries(obj))
res[k] = (res[k] || []).concat(v)
return res;
};
//
students = [
{id:1, name:'1', sex:'X', standard: 1, propName: 11, anotherSillyPropName:111,},
{id:2, name:'2', sex:'Y', standard: 2, propName: 22, anotherSillyPropName:222,},
{id:3, name:'3', sex:'Z', standard: 3, propName: 33, anotherSillyPropName:333,},
]
console.log(collect(students))
请注意,我们的collect
对“学生”及其“分数”一无所知,它只是对值矩阵进行转置。因此,您可以在需要此类功能的任何地方重用它。
总的来说,如果像getStudentGraphData
这样的特定于域的函数的增长往往超过几行,则有力地表明必须将基础计算从特定问题域中分解出来。