通用接口中的通用原型函数 ? Array<T>.GroupBy<K>(prop): { key: K, 数组: T[] }[]

Generic prototype function in a Generic interface ? Array<T>.GroupBy<K>(prop): { key: K, array: T[] }[]

所以我正在尝试完成这项工作:Array<T>.groupBy<KeyType> (property): {key: KeyType, array: Array<T> }[]; 代码如下所示:

type ArrayByParameter<T, KeyType = any> = string | ((item: T) => KeyType);
declare global {
  interface Array<T> {
    groupBy<KeyType = string>(
      property: ArrayByParameter<T,KeyType>
    ): { key: KeyType; array: T[] }[];
  }
}
if (!Array.prototype.groupBy) {
  Array.prototype.groupBy = function <KeyType = string>(
    property: ArrayByParameter<any, KeyType>
  ) {
    let callbackFunction: (item: any) => any;
    if (typeof property === "string") {
      callbackFunction = (mapObj) => mapObj[property];
    } else if (typeof property === "function") {
      callbackFunction = property;
    } else {
      throw "Parameter is not a string nor a function!";
    }
    // Edit version of : 
    return Object.entries(
      this.reduce(function (rv, x) {
        (rv[callbackFunction(x)] = rv[callbackFunction(x)] || []).push(
          x
        );
        return rv;
      }, {}) as { [key: string]: Array<any> }
    ).map(([key, array]) => {
      return { key, array };
    });
  };
}
type Characters = "A" | "B" | "C";
type Numbers = "1" | "2" | "3";
type SomeKeyType = `${Characters}-${Numbers}`;
// Same thing as below.
type SomeKeyType2 =
  | "A-1"
  | "A-2"
  | "A-3"
  | "B-1"
  | "B-2"
  | "B-3"
  | "C-1"
  | "C-2"
  | "C-3";
type SomeObject = {
  c: Characters;
  n: Numbers;
  // ...
};
const array: SomeObject[] = [
  { c: "A", n: 1 },
  { c: "A", n: 2 },
  { c: "A", n: 2 },
  { c: "B", n: 1 },
  // ...
];
const groupByArray: { key: SomeKeyType; array: SomeObject[] }[] =
  array.groupBy<KeyType>((obj: SomeObject) => `${obj.c}-${obj.n}`);
Result expected :
[
  {key: "A-1", array: [{c:A, n:1, /*...*/}]},
  {key:"A-2", array: [{/*...*/},{/*...*/}]},
  {key:"B-1", array:[{/*...*/}]},
  /*...*/
];

我在“Array.prototype.groupBy”的第 12 行遇到这样的错误:

Type '<KeyType = string>(property: ArrayByParameter<any, KeyType>) => { key: string; array: any[]; }[]' is not assignable to type '<KeyType = string>(property: ArrayByParameter<any, KeyType>) => { key: KeyType; array: any[]; }[]'. Type '{ key: string; array: any[]; }[]' is not assignable to type '{ key: KeyType; array: any[]; }[]'. Type '{ key: string; array: any[]; }' is not assignable to type '{ key: KeyType; array: any[]; }'. Types of property 'key' are incompatible. Type 'string' is not assignable to type 'KeyType'. 'KeyType' could be instantiated with an arbitrary type which could be unrelated to 'string'.ts(2322)

我认为我的 KeyType 定义有问题,但我找不到解决方案。 KeyType 是一个字符串,但也可以是我现在想要的模板文字类型。

所以 :

提前致谢! :)

我该如何解决这个问题才能使 KeyType 匹配?

这里的关键问题是 Object.entries 目前正在将 key 索引器硬编码为 string 而不是允许 K extends PropertyKeyPropertyKey 是一个可以在 JavaScript).

中索引到对象的所有类型的内置类型别名

没有什么我们不能用额外的环境类型定义来解决的:

declare module "our-global-ambient-module" {
  global {
    interface Array<T> {
      groupBy<KeyType extends PropertyKey = string>(
        property: ArrayByParameter<T, KeyType>
      ): { key: KeyType; array: T[] }[];
    }

    // What we've added - an overload to `Object.entries`
    // that will preserve the key's type.
    interface ObjectConstructor {
      entries<K extends PropertyKey, V>(object: any): [K, V][];
    }
  }
}

完整代码on the playground here

有没有办法让 T 作为 Array.prototype.groupBy 函数中的泛型

是的,只需添加一个类型参数并 type this 成为该类型:

Array.prototype.groupBy = function <T, KeyType extends PropertyKey = string>(
  this: T[],
  property: ArrayByParameter<any, KeyType>
) 

您还需要在 reduce 调用中注释 {} 的类型:

{} as {[X in KeyType]: T[]}