具有适当类型限制的 Typescript 通用数组记录功能

fri*_*doo 6 generics typescript

我正在尝试编写一个通用函数,它接受一个array: T[]和一个选择器K并返回一个Record<T[K], T>将数组中的每个元素从其属性之一映射到其自身的函数。

例如

const input1 = [{ name: "Alice" }, { name: "Bob" }];
const output1: Record<string, { name: string }> = toRecord(input1, "name");
// { Alice: { name: "Alice" }, Bob: { name: "Bob" } }

// -----------------------------------------------------------------
enum Name {
  Alice = "Alice",
  Bob = "Bob"
}
const input2 = [{ name: Name.Alice }, { name: Name.Bob }];
const output2: Record<Name, { name: Name }> = toRecord(input2, "name");
// { Alice: { name: "Alice" }, Bob: { name: "Bob" } }

// -----------------------------------------------------------------
interface Person {
  id: number;
  name: string;
  sibling?: Person;
}
const input3: Person[] = [{ id: 1, name: "Alice" }, { id: 2, name: "Bob" }];
const output3: Record<number, Person> = toRecord(input3, "id");
// { 1: { id: 1, name: "Alice" }, 2: { id: 2, name: "Bob" } }
Run Code Online (Sandbox Code Playgroud)

我不知道如何限制选择器属性的类型,K以便T[K]具有 aRecord接受作为键的类型,即string | number | symbol

这是我到目前为止所拥有的:

export function toRecord<T, K extends keyof T>(
  array: T[], 
  selector: K
): Record<T[K], T> {
  return array.reduce(
    (acc, item) => (acc[item[selector]] = item, acc), 
    {} as Record<T[K], T>
  )
}
Run Code Online (Sandbox Code Playgroud)

但这给了我以下错误T[K]

Type 'T[K]' does not satisfy the constraint 'string | number | symbol'.      
 Type 'T[keyof T]' is not assignable to type 'string | number | symbol'.
   Type 'T[string] | T[number] | T[symbol]' is not assignable to type 'string | number | symbol'.
     Type 'T[string]' is not assignable to type 'string | number | symbol'.
       Type 'T[string]' is not assignable to type 'symbol'.
         Type 'T[keyof T]' is not assignable to type 'symbol'.
           Type 'T[K]' is not assignable to type 'symbol'.
             Type 'T[keyof T]' is not assignable to type 'symbol'.
               Type 'T[string] | T[number] | T[symbol]' is not assignable to type 'symbol'.
                 Type 'T[string]' is not assignable to type 'symbol'.(2344)
Run Code Online (Sandbox Code Playgroud)

K我该如何限制T[K]string | number | symbol

斯塔克闪电战

Ale*_*yne 12

Typescript 不知道 T 的值作为键是有效的。

类型“T[K]”不满足约束“string |” 数量 | 象征'。

这就是说 thatT可以是{ a: boolean }or { a: () => void },这意味着 thatT[K]可以是布尔值或函数,这两者都不是有效的对象属性名称。

所以我会限制T为一个仅具有允许作为键的值的对象:

function toRecord<
  T extends { [K in keyof T]: string | number | symbol }, // added constraint
  K extends keyof T
>(array: T[], selector: K): Record<T[K], T> {
  return array.reduce((acc, item) => (acc[item[selector]] = item, acc), {} as Record<T[K], T>)
}
Run Code Online (Sandbox Code Playgroud)

操场


听起来您有一个属性可能不符合作为密钥的资格。您仍然可以强类型化,只需知道如果属性的值是一个对象,那么它不能是键,因此必须将其过滤掉。如前所述,对象属性名称的类型是string | number | symbol。因此,让我们保留这些值,并丢弃其余的。

type RecordableKeys<T> = {
  // for each key in T
  [K in keyof T]:
    // is the value a valid object key?
    T[K] extends string | number | symbol
    // Yes, return the key itself
    ? K
    // No. Return `never`
    : never
}[keyof T] // Get a union of the values that are not `never`.

interface Person {
  name: string,
  age: number
  sibling?: Person
}

type PersonRecordableKeys = RecordableKeys<Person> // 'name' | 'age'
Run Code Online (Sandbox Code Playgroud)

使用该辅助类型后,我们可以将Tin的约束更改toRecord为:

function toRecord<
  T extends { [P in RecordableKeys<T>]: string | number | symbol },
  K extends RecordableKeys<T>
>
Run Code Online (Sandbox Code Playgroud)

操场