array.groupBy 打字稿
array.groupBy in TypeScript
基本数组 class 有 .map
、.forEach
、.filter
和 .reduce
,但 .groupBy
我明显不存在,防止我不做类似
的事情
const MyComponent = (props:any) => {
return (
<div>
{
props.tags
.groupBy((t)=>t.category_name)
.map((group)=>{
[...]
})
}
</div>
)
}
我最终自己实现了一些东西:
class Group<T> {
key:string;
members:T[] = [];
constructor(key:string) {
this.key = key;
}
}
function groupBy<T>(list:T[], func:(x:T)=>string): Group<T>[] {
let res:Group<T>[] = [];
let group:Group<T> = null;
list.forEach((o)=>{
let groupName = func(o);
if (group === null) {
group = new Group<T>(groupName);
}
if (groupName != group.key) {
res.push(group);
group = new Group<T>(groupName);
}
group.members.push(o)
});
if (group != null) {
res.push(group);
}
return res
}
现在我可以做
const MyComponent = (props:any) => {
return (
<div>
{
groupBy(props.tags, (t)=>t.category_name)
.map((group)=>{
return (
<ul key={group.key}>
<li>{group.key}</li>
<ul>
{
group.members.map((tag)=>{
return <li key={tag.id}>{tag.name}</li>
})
}
</ul>
</ul>
)
})
}
</div>
)
}
工作得很好,但我需要包装列表而不是仅仅能够链接方法调用太糟糕了。
有更好的解决方案吗?
您可以将函数添加到应用程序中的数组原型(请注意,有些人不推荐这样做:Why is extending native objects a bad practice?):
Array.prototype.groupBy = function(/* params here */) {
let array = this;
let result;
/* do more stuff here*/
return result;
};
然后像这样在打字稿中创建一个界面:
.d.ts 版本:
interface Array<T>
{
groupBy<T>(func:(x:T) => string): Group<T>[]
}
或者在普通的 ts 文件中:
declare global {
interface Array<T>
{
groupBy<T>(func:(x:T) => string): Group<T>[]
}
}
那么你可以使用:
props.tags.groupBy((t)=>t.category_name)
.map((group)=>{
[...]
})
一个不错的选择可能是 lodash
。
npm install --save lodash
npm install --save-dev @types/lodash
只需导入它 import * as _ from 'lodash'
并使用。
示例
_.groupBy(..)
_.map(..)
_.filter(..)
您可以使用以下代码使用 Typescript 对内容进行分组。
const groupBy = <T, K extends keyof any>(list: T[], getKey: (item: T) => K) =>
list.reduce((previous, currentItem) => {
const group = getKey(currentItem);
if (!previous[group]) previous[group] = [];
previous[group].push(currentItem);
return previous;
}, {} as Record<K, T[]>);
// A little bit simplified version
const groupBy = <T, K extends keyof any>(arr: T[], key: (i: T) => K) =>
arr.reduce((groups, item) => {
(groups[key(item)] ||= []).push(item);
return groups;
}, {} as Record<K, T[]>);
因此,如果您具有以下结构和数组:
type Person = {
name: string;
age: number;
};
const people: Person[] = [
{
name: "Kevin R",
age: 25,
},
{
name: "Susan S",
age: 18,
},
{
name: "Julia J",
age: 18,
},
{
name: "Sarah C",
age: 25,
},
];
您可以像这样调用它:
const results = groupBy(people, i => i.name);
在这种情况下,将为您提供一个带有字符串键和 Person[] 值的对象。
这里有几个关键概念:
1- 您可以使用函数来获取密钥,这样您就可以使用 TS 推断功能来避免每次使用该函数时都必须键入泛型。
2- 通过使用 K extends keyof any
类型约束,您告诉 TS 正在使用的密钥需要是可以作为密钥 string | number | symbol
的东西,这样您就可以使用 getKey例如,将日期对象转换为字符串的函数。
3- 最后,您将获得一个对象,该对象具有键类型的键和数组类型的值。
使用 reduce
而不是 groupby
。假设 product
是你的 array
let group = product.reduce((r, a) => {
console.log("a", a);
console.log('r', r);
r[a.organization] = [...r[a.organization] || [], a];
return r;
}, {});
console.log("group", group);
在 2021 年 12 月的 TC39 会议期间,引入新 Array.prototype.groupBy
和 Array.prototype.groupByToMap
功能的提案已进入规范流程的第 3 阶段。
- https://github.com/tc39/proposal-array-grouping
- https://github.com/tc39/proposals/commit/b537605f01df50fd4901be5ce4aa0d02fe6e7193
根据上面链接的自述文件,这两个函数应该是这样的:
const array = [1, 2, 3, 4, 5];
// groupBy groups items by arbitrary key.
// In this case, we're grouping by even/odd keys
array.groupBy((num, index, array) => {
return num % 2 === 0 ? 'even': 'odd';
});
// => { odd: [1, 3, 5], even: [2, 4] }
// groupByToMap returns items in a Map, and is useful for grouping using
// an object key.
const odd = { odd: true };
const even = { even: true };
array.groupByToMap((num, index, array) => {
return num % 2 === 0 ? even: odd;
});
// => Map { {odd: true}: [1, 3, 5], {even: true}: [2, 4] }
虽然不能 100% 保证它会真正以上述形式在 JavaScript 的未来版本中结束(提案总是有可能被调整或删除,特别是出于兼容性原因),但很快就会在标准库中提供此 groupBy
功能,这是一个坚定的承诺。
通过连锁反应,这也意味着这些函数也将在 TypeScript 中可用。
基本数组 class 有 .map
、.forEach
、.filter
和 .reduce
,但 .groupBy
我明显不存在,防止我不做类似
const MyComponent = (props:any) => {
return (
<div>
{
props.tags
.groupBy((t)=>t.category_name)
.map((group)=>{
[...]
})
}
</div>
)
}
我最终自己实现了一些东西:
class Group<T> {
key:string;
members:T[] = [];
constructor(key:string) {
this.key = key;
}
}
function groupBy<T>(list:T[], func:(x:T)=>string): Group<T>[] {
let res:Group<T>[] = [];
let group:Group<T> = null;
list.forEach((o)=>{
let groupName = func(o);
if (group === null) {
group = new Group<T>(groupName);
}
if (groupName != group.key) {
res.push(group);
group = new Group<T>(groupName);
}
group.members.push(o)
});
if (group != null) {
res.push(group);
}
return res
}
现在我可以做
const MyComponent = (props:any) => {
return (
<div>
{
groupBy(props.tags, (t)=>t.category_name)
.map((group)=>{
return (
<ul key={group.key}>
<li>{group.key}</li>
<ul>
{
group.members.map((tag)=>{
return <li key={tag.id}>{tag.name}</li>
})
}
</ul>
</ul>
)
})
}
</div>
)
}
工作得很好,但我需要包装列表而不是仅仅能够链接方法调用太糟糕了。
有更好的解决方案吗?
您可以将函数添加到应用程序中的数组原型(请注意,有些人不推荐这样做:Why is extending native objects a bad practice?):
Array.prototype.groupBy = function(/* params here */) {
let array = this;
let result;
/* do more stuff here*/
return result;
};
然后像这样在打字稿中创建一个界面:
.d.ts 版本:
interface Array<T>
{
groupBy<T>(func:(x:T) => string): Group<T>[]
}
或者在普通的 ts 文件中:
declare global {
interface Array<T>
{
groupBy<T>(func:(x:T) => string): Group<T>[]
}
}
那么你可以使用:
props.tags.groupBy((t)=>t.category_name)
.map((group)=>{
[...]
})
一个不错的选择可能是 lodash
。
npm install --save lodash
npm install --save-dev @types/lodash
只需导入它 import * as _ from 'lodash'
并使用。
示例
_.groupBy(..)
_.map(..)
_.filter(..)
您可以使用以下代码使用 Typescript 对内容进行分组。
const groupBy = <T, K extends keyof any>(list: T[], getKey: (item: T) => K) =>
list.reduce((previous, currentItem) => {
const group = getKey(currentItem);
if (!previous[group]) previous[group] = [];
previous[group].push(currentItem);
return previous;
}, {} as Record<K, T[]>);
// A little bit simplified version
const groupBy = <T, K extends keyof any>(arr: T[], key: (i: T) => K) =>
arr.reduce((groups, item) => {
(groups[key(item)] ||= []).push(item);
return groups;
}, {} as Record<K, T[]>);
因此,如果您具有以下结构和数组:
type Person = {
name: string;
age: number;
};
const people: Person[] = [
{
name: "Kevin R",
age: 25,
},
{
name: "Susan S",
age: 18,
},
{
name: "Julia J",
age: 18,
},
{
name: "Sarah C",
age: 25,
},
];
您可以像这样调用它:
const results = groupBy(people, i => i.name);
在这种情况下,将为您提供一个带有字符串键和 Person[] 值的对象。
这里有几个关键概念:
1- 您可以使用函数来获取密钥,这样您就可以使用 TS 推断功能来避免每次使用该函数时都必须键入泛型。
2- 通过使用 K extends keyof any
类型约束,您告诉 TS 正在使用的密钥需要是可以作为密钥 string | number | symbol
的东西,这样您就可以使用 getKey例如,将日期对象转换为字符串的函数。
3- 最后,您将获得一个对象,该对象具有键类型的键和数组类型的值。
使用 reduce
而不是 groupby
。假设 product
是你的 array
let group = product.reduce((r, a) => {
console.log("a", a);
console.log('r', r);
r[a.organization] = [...r[a.organization] || [], a];
return r;
}, {});
console.log("group", group);
在 2021 年 12 月的 TC39 会议期间,引入新 Array.prototype.groupBy
和 Array.prototype.groupByToMap
功能的提案已进入规范流程的第 3 阶段。
- https://github.com/tc39/proposal-array-grouping
- https://github.com/tc39/proposals/commit/b537605f01df50fd4901be5ce4aa0d02fe6e7193
根据上面链接的自述文件,这两个函数应该是这样的:
const array = [1, 2, 3, 4, 5];
// groupBy groups items by arbitrary key.
// In this case, we're grouping by even/odd keys
array.groupBy((num, index, array) => {
return num % 2 === 0 ? 'even': 'odd';
});
// => { odd: [1, 3, 5], even: [2, 4] }
// groupByToMap returns items in a Map, and is useful for grouping using
// an object key.
const odd = { odd: true };
const even = { even: true };
array.groupByToMap((num, index, array) => {
return num % 2 === 0 ? even: odd;
});
// => Map { {odd: true}: [1, 3, 5], {even: true}: [2, 4] }
虽然不能 100% 保证它会真正以上述形式在 JavaScript 的未来版本中结束(提案总是有可能被调整或删除,特别是出于兼容性原因),但很快就会在标准库中提供此 groupBy
功能,这是一个坚定的承诺。
通过连锁反应,这也意味着这些函数也将在 TypeScript 中可用。