TypeScript حذف بسیاری از خصوصیات تو در تو
من در حال تلاش برای پیدا کردن روشی هستم تا تعداد n از خصوصیات پدر و تو در تو را در یک اینترفیس نادیده بگیرم. در مثال زیر، من در تلاش هستم یک خصوصیت پدر و دو خصوصیت تو در تو را نادیده بگیرم. نوع Omit تنها یک سطح را پشتیبانی میکند. توانستم چیزی نزدیک به آن پیدا کنم، اما هنوز کار نکرد:
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"
}
}
آیا راهی در تایپاسکریپت برای انجام این کار وجود دارد؟
من از تایپاسکریپت ۵ استفاده میکنم.
پاسخ شماره ۱
راهحل من که در زیر ارائه شده، همانطور که انتظار میرود برای کد نمونه در سؤال کار میکند. خوانندگان احتمالی آینده باید مراقب باشند: بر اساس تجربه من، چنین نگاشتهای نوع با تو در توی عمیق تمایل دارند که موارد حاشیهای عجیبی داشته باشند که، اگر غیرقابل قبول باشند، گاهی اوقات ممکن است نیاز به بازسازی کامل برای حل آنها باشد. من قصد ندارم در اینجا در مورد این مسائل نگران باشم.
در اینجا یک پیادهسازی ممکن از NestedOmit<T, K> آمده است:
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>
}
این از دوبارهنگاری کلیدها برای سرکوب هر کلید T که مستقیماً در K ظاهر میشود، استفاده میکند؛ این بخش تنها یک جایگزین برای Omit «معمولی» است، همانطور که در microsoft/TypeScript#41383 توضیح داده شده و در اینجا نشان داده شده است:
type NormalOmit<T, K extends PropertyKey> = {
[P in keyof T as P extends K ? never : P]: T[P]
}
تفاوت بین Omit<T, K> و NestedOmit<T, K> داخل نوع مقدار خصوصیت نگاشته شده اتفاق میافتد و نه در کلیدها. آن نوع خصوصیت معادل با NestedOmit<T[P], StripKey<K, P>> است، جایی که StripKey<K, P> به صورت زیر تعریف شده است
type StripKey<K extends PropertyKey, P extends PropertyKey> =
K extends `${Exclude<P, symbol>}.${infer R}` ? R : never
چون ویژگیهای NestedOmit به صورت NestedOmit نوشته شدهاند، این یک نوع بازگشتی تعریفشده است. ایده StripKey<T, P> این است که کلید جاری P و نقطه را از ابتدای هر عضو اتحاد K حذف کند، یا اگر عضو با P و نقطه شروع نشود، عضو را به طور کامل سرکوب کند. این از یک نوع شرطی توزیعی استفاده میکند تا به طور مستقل روی هر عضو اتحاد K عمل کند، و از استنتاج نوع رشتهای قالبی برای تجزیه رشته استفاده میکند. بیایید فقط این بخش را در عمل ببینیم:
type Demo = StripKey<"a.b" | "a.c.d" | "e" | "e.f" | "e.g", "a">;
// type Demo = "b" | "c.d"
میتوانید ببینید که اگر شما NestedOmit<{a: XYZ}, K> را با K به صورت "a.b" | "a.c.d" | "e" | "e.f" | "e.g", "a" فراخوانی میکردید، آنگاه ویژگی a در نهایت به صورت NestedOmit<XYZ, "b" | "c.d"> خواهد شد.
پس اینطور کار میکند. بیایید آن را روی مثال شما امتحان کنیم:
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"
>;
} */
آن نوع درست است، اما نمایش ممکن است آن چیزی که میخواهید نبینید، باشد، زیرا بهطور صریح به صورت NestedOmit نوشته شده است. ما میتوانیم از تکنیکی که در How can I see the full expanded contract of a Typescript type? توضیح داده شده است استفاده کنیم تا NestedOmit نوع خود را بهطور کامل گسترش دهد، اگر اهمیت داشته باشد:
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 است. حالا میبینیم:
type N = NestedOmit<IFirstObject,
"id" | "someProperty.firstPropertyToOmit" | "someProperty.secondPropertyToOmit"
>;
/* type N = {
revisionId: string;
someProperty: {
somePropertyToKeep: string;
};
} */
که دقیقاً همان چیزی است که میخواستی.
لینک زمین بازی به کد
پاسخ شماره ۲
پاسخ jcalz بسیار خوب است، اما من با آرایهها مشکل داشتم.
هر بار که من از NestedOmit روی یک نوع استفاده میکردم که دارای یک ویژگی از نوع آرایه بود، این آرایه به یک شیء با مقادیر اندیسگذاری شده تبدیل میشد ({[x: number]: string} به جای string[])
پس من اکنون به جای آن از این استفاده میکنم:
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;
