TypeScript Çok sayıda iç içe geçmiş özelliği hariç tut
Bir arayüzde n sayıda üst ve iç içe geçmiş özelliği atlamanın bir yolunu aramaya çalışıyorum. Aşağıdaki örnekte, bir üst özelliği ve iki iç içe geçmiş özelliği atlamaya çalışıyorum. Omit türü yalnızca bir seviye derinliğe iner. Buna yakın bir şey bulabildim, ama yine de çalışmadı:
interface IFirstObject {
id: string;
revisionId: string;
someProperty: ISecondObject;
}
interface ISecondObject {
somePropertyToKeep: string;
firstPropertyToOmit: string;
secondPropertyToOmit: string;
}
/// How can we create a type to behave like the below result I am looking for?
type SomeNestedOmitType<TEntity, TOmissions> = {...}
// Desired Result
const myObject: SomeNestedOmitType<IFirstObject, "id" | "someProperty.firstPropertyToOmit" | "someProperty.secondPropertyToOmit"> = {
revisionId: "some revision id",
someProperty: {
somePropertyToKeep: "some property to keep"
}
}
TypeScript'te bunu yapmanın bir yolu var mı?
TypeScript 5 kullanıyorum.
Cevap #1
Aşağıda sunduğum çözüm, sorudaki örnek kod için beklendiği gibi çalışıyor. Gelecekteki olası okuyucuların dikkatli olması gerekir: deneyimime göre, bu kadar derin iç içe geçmiş tür eşlemeleri tuhaf uç durumlara sahip olma eğilimindedir ve eğer kabul edilemezse, bazen çözmek için tamamen yeniden yapılandırma gerekebilir. Burada bunlarla uğraşmayacağım.
NestedOmit<T, K> için olası bir uygulama şöyle olabilir:
type NestedOmit<T, K extends PropertyKey> = {
[P in keyof T as P extends K ? never : P]:
NestedOmit<T[P], K extends `${Exclude<P, symbol>}.${infer R}` ? R : never>
}
Bu, K içinde doğrudan görünen T tuşlarının herhangi birini bastırmak için tuş yeniden eşlemesini kullanır; bu kısım, microsoft/TypeScript#41383'te açıklandığı ve burada gösterildiği gibi, "normal" Omit için sadece bir alternatiftir:
type NormalOmit<T, K extends PropertyKey> = {
[P in keyof T as P extends K ? never : P]: T[P]
}
Omit<T, K> ve NestedOmit<T, K> arasındaki fark, anahtarlar değil, eşlenen özellik değeri türü içinde gerçekleşir. Bu özellik türü, NestedOmit<T[P], StripKey<K, P>> ile eşdeğerdir; burada StripKey<K, P> şu şekilde tanımlanmıştır
type StripKey<K extends PropertyKey, P extends PropertyKey> =
K extends `${Exclude<P, symbol>}.${infer R}` ? R : never
NestedOmit’un özellikleri NestedOmit açısından yazıldığından, bu özyinelemeli tanımlanmış bir tiptir. StripKey<T, P>’in fikri, mevcut anahtar P’yi ve her K birliğinin başındaki noktayı kesmek ya da üye P ve nokta ile başlamıyorsa tamamen bastırmaktır. Bu, K’nin her birlik üyesi üzerinde bağımsız olarak çalışmak için dağıtıcı koşullu tip kullanır ve string’i çözümlemek için şablon metin tipi çıkarımını kullanır. Hadi sadece bu kısmını uygulamada görelim:
type Demo = StripKey<"a.b" | "a.c.d" | "e" | "e.f" | "e.g", "a">;
// type Demo = "b" | "c.d"
Şunu görebilirsiniz ki, eğer K olarak "a.b" | "a.c.d" | "e" | "e.f" | "e.g", "a" ile NestedOmit<{a: XYZ}, K> çağırıyor olsaydınız, o zaman a özelliği NestedOmit<XYZ, "b" | "c.d"> olarak sonuçlanırdı.
Demek böyle çalışıyor. Haydi bunu senin örneğinde test edelim:
interface IFirstObject {
id: string;
revisionId: string;
someProperty: ISecondObject;
}
interface ISecondObject {
somePropertyToKeep: string;
firstPropertyToOmit: string;
secondPropertyToOmit: string;
}
type N = NestedOmit<IFirstObject,
"id" | "someProperty.firstPropertyToOmit" | "someProperty.secondPropertyToOmit"
>;
/* type N = {
revisionId: string;
someProperty: NestedOmit<ISecondObject,
"firstPropertyToOmit" | "secondPropertyToOmit"
>;
} */
Bu tür doğru, ancak görüntülemek istediğiniz şey olmayabilir, çünkü açıkça NestedOmit cinsinden yazılmıştır. Eğer önemliyse, NestedOmit türünü tamamen genişletmek için How can I see the full expanded contract of a Typescript type? adresinde açıklanan bir teknik kullanabiliriz:
type NestedOmit<T, K extends PropertyKey> = {
[P in keyof T as P extends K ? never : P]:
NestedOmit<T[P], K extends `${Exclude<P, symbol>}.${infer R}` ? R : never>
} extends infer O ? { [P in keyof O]: O[P] } : never;
⋯ extends infer O ? { [P in keyof O]: O[P] } : never kısmı bunu yapan bölüm. Şimdi görüyoruz:
type N = NestedOmit<IFirstObject,
"id" | "someProperty.firstPropertyToOmit" | "someProperty.secondPropertyToOmit"
>;
/* type N = {
revisionId: string;
someProperty: {
somePropertyToKeep: string;
};
} */
bu tam olarak istediğin şey.
Koda yönelik oyun alanı bağlantısı
Cevap #2
jcalz'in cevabı çok güzel, ama dizilerle ilgili bir sorunum vardı.
Her seferinde, içinde diz türünde bir özelliğe sahip bir tipte NestedOmit kullandığımda, bu dizi, dizinin değerleriyle bir nesneye dönüştürülüyordu ({[x: number]: string} yerine string[])
Yani şimdi bunun yerine bunu kullanıyorum:
export type NestedOmit<T, K extends PropertyKey> =
T extends Array<infer U>
? Array<NestedOmit<U, K>> // If T is an array, apply NestedOmit to its elements
: {
[P in keyof T as P extends K ? never : P]: NestedOmit<
T[P],
K extends `${Exclude<P, symbol>}.${infer R}` ? R : never
>;
} extends infer O
? { [P in keyof O]: O[P] }
: never;
