繁体   English   中英

TypeScript 中的 array.groupBy

[英]array.groupBy in TypeScript

基本数组类有.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>
    )
}

效果很好,但是我需要包装列表而不仅仅是能够链接方法调用,这太糟糕了。

有更好的解决方案吗?

您可以使用以下代码使用 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 string | number | symbol string | number | symbol ,这样您就可以使用 getKey 函数将 Date 对象转换为字符串。

3- 最后,您将获得一个具有键类型的键和数组类型的值的对象。

您可以将该函数添加到应用程序中的数组原型中(请注意,有些人不建议这样做: 为什么扩展本机对象是一种不好的做法? ):

Array.prototype.groupBy = function(/* params here */) { 
   let array = this; 
   let result;
   /* do more stuff here*/
   return result;
}; 

然后在 typescript 中创建一个接口,如下所示:

.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)=>{
                    [...]
                })

而不是groupby使用reduce 假设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);

一个不错的选择可能是lodash

npm install --save lodash
npm install --save-dev @types/lodash

只需导入它import * as _ from 'lodash'并使用。

例子

_.groupBy(..)
_.map(..)
_.filter(..)

在 2021 年 12 月的 TC39 会议期间,引入新的Array.prototype.groupByArray.prototype.groupByToMap函数的提案已进入规范流程的第 3 阶段。

根据上面链接的自述文件,这两个函数应该是这样的:

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 中可用。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM