typescript - Nesnede dinamik bir anahtar değeri ile birleşim tipi nasıl kullanılır?
İki stringin birleşim tipini kullanmaya çalışıyorum, stringlerden birini bir argüman olarak geçiyorum, sonra bu argümanı bir nesne özelliği için anahtar olarak kullanmak istiyorum.
type TimetabledClass = {
classCode: string, students: string[]
} | { classCode: string, teachers: string[] }
function getTimetabledClassEnrolments(
type: 'students' | 'teachers',
classCode: string
) {
const timetabledClasses: TimetabledClass[] = []
const enrolments = ['person1', 'person2']
timetabledClasses.push({
classCode,
[type]: enrolments // error!
// Argument of type '{ [x: string]: string | string[]; classCode: string; }'
// is not assignable to parameter of type 'TimetabledClass'.
})
return timetabledClasses
}
Cevap #1
Sorununuz, TypeScript'teki bilinen bir sorundan kaynaklanmaktadır; bkz. microsoft/TypeScript#13948. Tipi tek bir literal tipten başka bir şey olan bir hesaplanmış özellik adınız varsa, derleyici anahtarın tipini stringe kadar genişletecek ve bu da sıklıkla istenmeyen bir string indeks imzasına yol açacaktır:
function computed<K extends string>(k: K, s: string) {
const literal = { ["a"]: 123 };
// const literal: { a: number }
const union = { [Math.random() < 0.5 ? "x" : "y"]: "abc" };
// const union: { [x: string]: string }
const templateLiteral = { [`a${s}b`]: "abc" };
// const templateLiteral: { [x: string]: string }
const generic = { [k]: 123 };
// const generic: { [x: string]: number }
}
Sadece literal'in belirli bir anahtara sahip bir türde olduğunu görebilirsiniz. Geri kalanlar ise tümüyle bir string indeks imzasına genişletilmiştir.
Kodunuzda, type'ın türü string literal birleşimi olduğundan, { classCode, [type]: enrolments } değeri { [x: string]: string | string[]; classCode: string; } türünde görülür ve bu da TimetabledClass'e atanamaz.
Her zaman yaptığınız şeyin doğru olduğunu iddia edebilirsiniz:
timetabledClasses.push({
classCode,
[type]: enrolments
} as TimetabledClass); // okay
ancak microsoft/TypeScript#13948 için bir geçici çözüm ile biraz daha iyi yapmak mümkün. Genellikle kv(k, v) adında bir yardımcı fonksiyon kullanırım, bu fonksiyon k anahtarı ve v değeri olan bir özelliğe sahip bir nesne üretir ve daha spesifik bir türe sahiptir:
function kv<K extends PropertyKey, V>(
k: K, v: V
): { [P in K]: { [Q in P]: V } }[K] {
return { [k]: v } as any
}
Evet, bir doğrulama kullanıyor, ama mesele şu ki bu fonksiyon sadece bir kez yazılmak zorunda ama birçok yerde kullanılabilir, bu da toplam güvenlik açıklarının sayısını azaltır.
Dönüş tipi temelde bir dağıtılabilir Record<K, V> tipidir; burada anahtar tiplerinin birleşimi, çıktı tiplerinin birleşimi haline gelir. İşte yukarıdaki computed() örnekleri ile nasıl çalıştığı:
function withKv<K extends string>(k: K, s: string) {
const literal = kv("a", 123);
// const literal: { a: number }
const union = kv(Math.random() < 0.5 ? "x" : "y", "abc");
// const union: { x: string; } | { y: string; }
const templateLiteral = kv(`a${s}b`, "abc");
// const templateLiteral: { [x: `a${string}b`]: string }
const generic = kv(k, 123);
// const generic: { [P in K]: { [Q in P]: number; }; }[K]
}
literal türü daha önceki ile aynı literal türdür, ancak diğer değerlerin türleri artık daha güçlüdür. union türü kendisi bir birleşimdir. templateLiteral türü bir şablon dizesi desen indeks imzasıdır ve generic türü hala generiktir.
Yani bunu elde etmek için kodunuzda birlik desteğini kullanabiliriz:
timetabledClasses.push({
classCode,
...kv(type, enrolments)
}); // okay
Çıktıyı kv() fonksiyonundan alıp inline bir özelliğe dönüştürmek için spread operatörünü kullandığım yer.
Bu, elemanın türü olarak görüldüğü için hatasız derlenir
const val = { classCode, ...kv(type, enrolments) };
// const val: { students: string[]; classCode: string } |
// { teachers: string[]; classCode: string }
istendiği gibi TimetabledClass ile eşdeğerdir.
Koda yönelik oyun alanı bağlantısı
