Typescript Genel Dizi'yi uygun tür kısıtlamalarıyla Kayıt fonksiyonuna dönüştürme


Bir array: T[] ve bir seçici K alan ve dizideki her öğeyi bir özelliğinden kendisine eşleyen bir Record<T[K], T> döndüren genel bir işlev yazmaya çalışıyorum.

ör.

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" } }

Seçici özelliği K türünü, T[K] için Record'un anahtar olarak kabul edeceği türde yani string | number | symbol olacak şekilde nasıl kısıtlayacağımı çözemedim.

Şu ana kadar elimde olan bu:

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>
  )
}

Ama bu bana T[K] üzerinde aşağıdaki hatayı veriyor:

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)

T[K] string | number | symbol olacak şekilde K'yi nasıl kısıtlayabilirim?

Stackblitz

Cevap #1

Typescript, T değerlerinin anahtar olarak geçerli olduğunu bilmiyor.

‘T[K]’ türü ‘string | number | symbol’ kısıtını karşılamıyor.

Bu, T'nin { a: boolean } veya { a:() => void } olabileceğini söylüyor ve bu, T[K]'nin bir boolean veya bir fonksiyon olabileceği anlamına gelir, bunların hiçbiri nesne özellik adları olarak geçerli değildir.

Bu yüzden T öğesini sadece anahtar olarak kullanılmasına izin verilen değerlere sahip bir nesne ile sınırlardım:

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>)
}

Oyun alanı

Görünüşe göre anahtar olarak uygun olmayabilecek özellikleriniz var. Bunu yine de güçlü bir şekilde yazabilirsiniz, sadece özelliğin değeri bir nesne ise, bir anahtar olamayacağını bilmelisiniz; bu yüzden bunlar filtrelenmelidir. Daha önce belirtildiği gibi, bir nesne özelliği adının türü string | number | symbol'dür. O yüzden bu değerleri tutalım ve geri kalanını atlayalım.

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'

Bu yardımcı tür yerinde olduğunda, toRecord içindeki T kısıtlamasını şuna değiştirebiliriz:

function toRecord<
  T extends { [P in RecordableKeys<T>]: string | number | symbol },
  K extends RecordableKeys<T>
>

Oyun alanı

Cevap #2

Typescript, T'nin aslında bir kayıt olduğunu kesin olarak bilmek istiyor:

export function toRecord<T extends Record<string, any>, K extends keyof T>(array: T[], selector: K): Record<T[K], T> {
  return array.reduce((acc, item) => ({ ...acc, [item[selector]]: item }), {} as Record<T[K], T>)
}

İlgili makaleler