Typescript泛型:将T[K]类型的键Map到T本身的值(按自己的属性值对T[]进行分组)

qoefvg9y  于 12个月前  发布在  TypeScript
关注(0)|答案(1)|浏览(99)

我想写一个泛型函数,它可以创建一个对象的Map,其中键是对象本身的属性,因此可以根据它们的一个属性的值对它们进行分组。
最后我写了下面的代码:

type PartialRecord<K extends keyof any, T> = {
  [P in K]?: T;
};

const innerMap = <T, K extends keyof any>(
  array: T[], customKey: keyof T
): PartialRecord<K, T> => {
    const innerMap: PartialRecord<K, T> = {};
    for (let i = 0; i < array.length; ++i) {
      innerMap[array[i][customKey] as K] = array[i];
    }
    return innerMap;
  }
}

字符串
它给出了错误:Conversion of type 'T[keyof T]' to type 'K' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first的。
所以你会得到:innerMap[array[i][customKey] as unknown as K,这(双重)违背了打字的目的。
它应该做的是:

export class CuteKitty {
  constructor(readonly furColor: string, readonly eyeColor: string) {}
}
const redKittyBlueEyes = new CuteKitty('red', 'blue');
const redKittyRedEyes = new CuteKitty('red', 'red');
const whiteKittyRedEyes = new CuteKitty('white', 'red');

const furColorMap = innerMap([redKittyBlueEyes, redKittyRedEyes, whiteKittyRedEyes], 'furColor');
// Result: {
//   red: [redKittyBlueEyes, redKittyRedEyes];
//   white: [whiteKittyRedEyes];
// }

const eyeColorMap = innerMap([redKittyBlueEyes, redKittyRedEyes, whiteKittyRedEyes], 'eyeColor');
// Result: {
//   blue: [redKittyBlueEyes];
//   red: [redKittyRedEyes, whiteKittyRedEyes];
// }


如果不对innerMap[array[i][customKey]] as unknown as K进行强制转换,我如何确保array[i][customKey]是一个可用于索引的类型(特别是我替换K的类型)?

vmpqdwk3

vmpqdwk31#

一路沿着学到了一些东西,所以我调整了几次我的答案。最后的解决方案是最有用的imo。

1.原始答案

找到了一个解决方案,而不必使用肮脏的修复铸造“作为未知的K”。
我创建了这些自定义类型:

type TRecordKey = string | number | symbol;
type TRecurringRecord<K extends T[keyof T] & TRecordKey, T> = {
  [P in K]?: T
}

字符串
然后我调整了上面的代码如下:

const recurringMap<K extends T[keyof T] & TRecordKey, T>(
  array: T[],
  customKey: keyof T
): TRecurringRecord<K, T> {
  const recurringMap: TRecurringRecord<K, T> = {};
  for (let i = 0; i < array.length; ++i) {
    recurringMap[array[i][customKey] as K] = array[i];
  }
  return recurringMap;
}


现在你可以加入一个对象数组,和一个属性的字符串名称,它会在一个map中根据该属性对这些对象进行分组。

2.答案03-10-2022

为基本相同的事情编写更简单的代码。

  • 删除了具有Indexable类型的TRecordKeyTRecurringRecord,它只是声明键应该是字符串。这是处理“两种类型都不与另一种类型充分重叠”警告的简单得多的方法。(如果对象具有数字/符号属性,则不起作用)。
  • 不需要再用“K”了。
  • Map到类型T的数组而不是仅1个示例。
type TIndexable = {
   [key: string]: any;
};

const mapFromProperty = <T extends TIndexable>(
  objects: T[], propertyName: keyof T
): Record<string, T[]> => {
  return objects.reduce((accumulator: Record<string, T[]>, current: T): Record<string, T[]> => {
    if (!accumulator[current[propertyName]]) {
      accumulator[current[propertyName]] = [current];
    } else {
      accumulator[current[propertyName]].push(current);
    }
    return accumulator;
  }, {});
}

3.答案01-08-2023

  • 修复了函数结果的推断类型(键应该是T[K]而不是K)。
  • 重新添加并改进了K的强类型,因为我个人通常只希望键是number | string | symbol类型,而不是对象。
  • 删除了TIndexable类型,只使用Record<any, any>,这将允许任何对象。
type TRecordKey = string | number | symbol;

// Mapping to 1 instance of T in case of unique keys
const recurringMapSingle = <
    K extends (T[K] extends Record<any, any> ? never : (keyof T & TRecordKey)),
    T extends Record<any, any>
  >(objects: T[], key: K): Partial<Record<T[K], T>> => {
    return objects.reduce((acc: Partial<Record<T[K], T>>, x: T) => {
        acc[x[key]] = x;
        return acc;
    }, {});
}

// Mapping to multiple instances of T
const recurringMapMultiple = <
    K extends (T[K] extends Record<any, any> ? never : (keyof T & TRecordKey)),
    T extends Record<any, any>
  >(objects: T[], key: K): Partial<Record<T[K], T[]>> => {
    return objects.reduce((acc: Partial<Record<T[K], T[]>>, x: T) => {
        acc[x[key]]?.push(x) ?? (acc[x[key]] = [x]);
        return acc;
    }, {});
}


T[K] extends Record<any, any> ? never : (keyof T & TRecordKey)意味着如果键K处的属性的类型是对象,则never也可以是这种情况,因此当您尝试将该类型的属性的名称传递到函数中时,它会生成错误。否则,K必须是T的键,并且还必须是stringnumbersymbol

相关问题