javascript - การแทนที่ประเภทคุณสมบัติของอินเทอร์เฟซที่กำหนดในไฟล์ Typescript d.ts
มีวิธีใดในการเปลี่ยนประเภทของคุณสมบัติอินเทอร์เฟซที่กำหนดใน *.d.ts ใน TypeScript ไหม?
ตัวอย่าง: อินเทอร์เฟซใน x.d.ts ถูกนิยามเป็น
interface A {
property: number;
}
ฉันต้องการเปลี่ยนมันในไฟล์ TypeScript ที่ฉันเขียนด้วย
interface A {
property: Object;
}
หรือแม้แต่นี่ก็ใช้ได้
interface B extends A {
property: Object;
}
วิธีการนี้จะใช้ได้ไหม? มันไม่ทำงานเมื่อฉันลองบนระบบของฉัน แค่อยากยืนยันว่ามันเป็นไปได้หรือเปล่า?
คำตอบ #1
ฉันใช้วิธีที่กรองฟิลด์ก่อนแล้วจึงรวมเข้าด้วยกัน
อ้างอิง ยกเว้นคุณสมบัติจากชนิด
interface A {
x: string
}
export type B = Omit<A, 'x'> & { x: number };
สำหรับอินเทอร์เฟซ:
interface A {
x: string
}
interface B extends Omit<A, 'x'> {
x: number
}
คำตอบ #2
type ModifiedType = Modify<OriginalType, {
a: number;
b: number;
}>
interface ModifiedInterface extends Modify<OriginalType, {
a: number;
b: number;
}> {}
ได้รับแรงบันดาลใจจากโซลูชัน extends Omit ของ ZSkycat ผมจึงคิดสิ่งนี้ขึ้นมา:
type Modify<T, R> = Omit<T, keyof R> & R; // before [email type Modify<T, R> = Pick<T, Exclude<keyof T, keyof R>> & R
ตัวอย่าง :
interface OriginalInterface {
a: string;
b: boolean;
c: number;
}
type ModifiedType = Modify<OriginalInterface , {
a: number;
b: number;
}>
// ModifiedType = { a: number; b: number; c: number; }
ไปทีละขั้นตอน:
type R0 = Omit<OriginalType, 'a' | 'b'> // { c: number; }
type R1 = R0 & {a: number, b: number } // { a: number; b: number; c: number; }
type T0 = Exclude<'a' | 'b' | 'c' , 'a' | 'b'> // 'c'
type T1 = Pick<OriginalType, T0> // { c: number; }
type T2 = T1 & {a: number, b: number } // { a: number; b: number; c: number; }
ประเภทยูทิลิตี้ของ TypeScript
การปรับแต่งลึก v3
interface Original {
a: {
a: string
b: { a: string }
c: string
d: string // <- keep this one
}
}
interface Overrides {
a: {
a: { a: number } // <- overwrite string with object
b: number // <- overwrite object with number
c: number // <- overwrite string with number
e: number // <- new property
}
}
type ModifiedType = ModifyDeep<Original, Overrides>
interface ModifiedInterface extends ModifyDeep<Original, Overrides> {}
const example: ModifiedType = {
a: {
a: { a: number },
b: number,
c: number,
d: string,
e: number,
}
}
ค้นหา ModifyDeep ด้านล่าง
คำตอบ #3
คำตอบสั้นๆ สำหรับคนขี้เกียจอย่างฉัน:
type Overrided = Omit<YourInterface, 'overrideField'> & { overrideField: <type> };
interface Overrided extends Omit<YourInterface, 'overrideField'> {
overrideField: <type>
}
คำตอบ #4
คุณไม่สามารถเปลี่ยนประเภทของคุณสมบัติที่มีอยู่ได้
คุณสามารถเพิ่มคุณสมบัติได้:
interface A {
newProperty: any;
}
แต่การเปลี่ยนประเภทของสิ่งที่มีอยู่:
interface A {
property: any;
}
ส่งผลให้เกิดข้อผิดพลาด:
การประกาศตัวแปรครั้งถัดไปต้องมีชนิดเดียวกัน ตัวแปร 'property' ต้องเป็นชนิด 'number' แต่ที่นี่มีชนิด 'any'
คุณสามารถมีส่วนติดต่อของคุณเองซึ่งขยายจากส่วนติดต่อที่มีอยู่ได้แน่นอน ในกรณีนั้น คุณสามารถแทนที่ชนิดเพียงให้เป็นชนิดที่เข้ากันได้เท่านั้น เช่น:
interface A {
x: string | number;
}
interface B extends A {
x: number;
}
อย่างไรก็ตาม คุณควรหลีกเลี่ยงการใช้ Object เป็นชนิดข้อมูล แทนที่จะใช้ ชนิดข้อมูล any
ในเอกสารสำหรับประเภท any ระบุว่า:
ประเภท any เป็นวิธีที่ทรงพลังในการทำงานกับ JavaScript ที่มีอยู่ ช่วยให้คุณสามารถเลือกเปิดหรือปิดการตรวจสอบประเภทแบบค่อยเป็นค่อยไปในระหว่างการคอมไพล์ คุณอาจคิดว่า Object จะมีบทบาทที่คล้ายกันเหมือนในภาษาอื่น ๆ แต่ตัวแปรประเภท Object อนุญาตให้คุณกำหนดค่าใด ๆ ให้กับมันเท่านั้น - คุณไม่สามารถเรียกใช้เมธอดใด ๆ แบบสุ่มกับมันได้ แม้กระทั่งเมธอดที่มีอยู่จริง:
let notSure: any = 4;
notSure.ifItExists(); // okay, ifItExists might exist at runtime
notSure.toFixed(); // okay, toFixed exists (but the compiler doesn't check)
let prettySure: Object = 4;
prettySure.toFixed(); // Error: Property 'toFixed' doesn't exist on type 'Object'.
คำตอบ #5
เพิ่มเติมจากคำตอบของ @zSkycat เล็กน้อย คุณสามารถสร้าง generic ที่รับสองประเภทของออบเจ็กต์และคืนค่าประเภทที่รวมกันโดยที่สมาชิกของประเภทที่สองจะมีผลครอบงำสมาชิกของประเภทแรกได้
type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>
type Merge<M, N> = Omit<M, Extract<keyof M, keyof N>> & N;
interface A {
name: string;
color?: string;
}
// redefine name to be string | number
type B = Merge<A, {
name: string | number;
favorite?: boolean;
}>;
let one: A = {
name: 'asdf',
color: 'blue'
};
// A can become B because the types are all compatible
let two: B = one;
let three: B = {
name: 1
};
three.name = 'Bee';
three.favorite = true;
three.color = 'green';
// B cannot become A because the type of name (string | number) isn't compatible
// with A even though the value is a string
// Error: Type {...} is not assignable to type A
let four: A = three;
คำตอบ #6
ให้ Omit คุณสมบัติเมื่อขยายอินเทอร์เฟซ:
interface A {
a: number;
b: number;
}
interface B extends Omit<A, 'a'> {
a: boolean;
}
คำตอบ #7
วิธีแก้ไขสำหรับการเขียนทับ สอง หรือ มากกว่าสองคุณสมบัติ ของอินเทอร์เฟซ:
interface BaseInterface {
a: string;
b: string;
c: string;
}
interface ModifiedInterface extends Omit<BaseInterface, 'a' | 'b'> {
a?: string; // make it optional
b: boolean; // make it boolean
d: number; // add another property
}จากเอกสาร TypeScript
คำตอบ #8
ฉันได้สร้างประเภทนี้ขึ้นซึ่งทำให้ฉันสามารถแทนที่อินเทอร์เฟซที่ซ้อนกันได้อย่างง่ายดาย:
export type DeepPartialAny<T> = {
[P in keyof T]?: T[P] extends Obj ? DeepPartialAny<T[P]> : any;
};
export type Override<A extends Obj, AOverride extends DeepPartialAny<A>> = { [K in keyof A]:
AOverride[K] extends never
? A[K]
: AOverride[K] extends Obj
? Override<A[K], AOverride[K]>
: AOverride[K]
};
และจากนั้นคุณสามารถใช้มันแบบนั้นได้:
interface Foo {
Bar: {
Baz: string;
};
}
type Foo2 = Override<Foo, { Bar: { Baz: number } }>;
const bar: Foo2['Bar']['Baz'] = 1; // number;
คำตอบ #9
สำหรับการจำกัดประเภทของคุณสมบัติ การใช้ extend แบบง่ายๆ ทำงานได้อย่างสมบูรณ์แบบ เช่นเดียวกับในคำตอบของ Nitzan:
interface A {
x: string | number;
}
interface B extends A {
x: number;
}
สำหรับการขยาย หรือโดยทั่วไป การแทนที่ ประเภท คุณสามารถทำตามวิธีแก้ของ Zskycat:
interface A {
x: string
}
export type B = Omit<A, 'x'> & { x: number };
แต่หากอินเทอร์เฟซ A ของคุณกำลังขยายอินเทอร์เฟซทั่วไป คุณจะสูญเสียประเภทที่กำหนดเองของคุณสมบัติที่เหลือของ A เมื่อใช้ Omit
เช่น
interface A extends Record<string | number, number | string | boolean> {
x: string;
y: boolean;
}
export type B = Omit<A, 'x'> & { x: number };
let b: B = { x: 2, y: "hi" }; // no error on b.y!
เหตุผลก็คือ Omit ภายในจะวนลูปเฉพาะคีย์ Exclude<keyof A, 'x'> ซึ่งในกรณีของเราจะเป็น string | number ทั่วไป ดังนั้น B จะกลายเป็น {x: number; } และยอมรับคุณสมบัติเพิ่มเติมใด ๆ ที่มีชนิดเป็น number | string | boolean
เพื่อแก้ปัญหานั้น ฉันจึงคิดค้นประเภทยูทิลิตี้ OverrideProps แบบใหม่ดังนี้:
type OverrideProps<M, N> = { [P in keyof M]: P extends keyof N ? N[P] : M[P] };
ตัวอย่าง :
type OverrideProps<M, N> = { [P in keyof M]: P extends keyof N ? N[P] : M[P] };
interface A extends Record<string | number, number | string | boolean> {
x: string;
y: boolean;
}
export type B = OverrideProps<A, { x: number }>;
let b: B = { x: 2, y: "hi" }; // error: b.y should be boolean!
คำตอบ #10
การปรับแต่งลึก v4
ตอนนี้ฉันกำลังสร้างโซลูชันที่แข็งแกร่งขึ้นใน gist ของฉัน ซึ่งจัดการกับอาเรย์ได้ดีขึ้นและอนุญาตให้ลบคีย์หรือแก้ไข ? ความเป็นตัวเลือก ของมันได้
interface Original {
x: {
a: string
b: string
}
}
interface Overrides {
x: {
a: never // <- this key will be deleted
b?: string // <- this will become optional
}
}
/* result = {
x: {
b?: string
}
} */
การปรับแต่งลึก v3
*หมายเหตุ รุ่น 2 อยู่ในประวัติของคำตอบนี้
interface Original {
a: {
a: string
b: { a: string }
c: string
d: string // <- keep this one
}
}
interface Overrides {
a: {
a: { a: number } // <- overwrite string with object
b: number // <- overwrite object with number
c: number // <- overwrite string with number
e: number // <- new property
}
}
type ModifiedType = ModifyDeep<Original, Overrides>
interface ModifiedInterface extends ModifyDeep<Original, Overrides> {}
ผลลัพธ์
const example: ModifiedType = {
a: {
a: { a: number },
b: number,
c: number,
d: string,
e: number,
}
}
รหัส
type ModifyDeep<A, B extends DeepPartialAny<A>> = {
[K in keyof A | keyof B]: // For all keys in A and B:
K extends keyof A // ───┐
? K extends keyof B // ───┼─ key K exists in both A and B
? A[K] extends AnyObject // │ ┴──┐
? B[K] extends AnyObject // │ ───┼─ both A and B are objects
? B[K] extends AnyFunction // │ │ ├─ Avoid deeply modifying functions which results in {}
? B[K] // │ │ │
: ModifyDeep<A[K], B[K]> // │ │ └─── We need to go deeper (recursively)
: B[K] // │ ├─ B is a primitive 🠆 use B as the final type (new type)
: B[K] // │ └─ A is a primitive 🠆 use B as the final type (new type)
: A[K] // ├─ key only exists in A 🠆 use A as the final type (original type)
: B[K] // └─ key only exists in B 🠆 use B as the final type (new type)
}
type AnyObject = Record<string, any>
type AnyFunction = (...args: any[]) => any
// This type is here only for some intellisense for the overrides object
type DeepPartialAny<T> = {
/** Makes each property optional and turns each leaf property into any, allowing for type overrides by narrowing any. */
[P in keyof T]?: T[P] extends AnyObject ? DeepPartialAny<T[P]> : any
}
*หมายเหตุ, ประเภท DeepPartialAny มีไว้เพียงเพื่อเป็นคำแนะนำประเภท แต่ ไม่ สมบูรณ์แบบ ทางด้านเทคนิค ตรรกะของประเภท ModifyDeep อนุญาตให้แทนที่โหนดใบ {a: string} ด้วยออบเจ็กต์ {a: {b: ... }} และในทางกลับกัน แต่ DeepPartialAny จะรายงานข้อผิดพลาดเมื่อมีการแทนที่ object ด้วยค่าพื้นฐานแบน ๆ ด้วยข้อความข้อผิดพลาดเช่นนี้
Type 'number' has no properties in common with type 'DeepPartialAny<{ a: string; }>'
อย่างไรก็ตาม คุณสามารถละเว้นข้อผิดพลาดได้อย่างปลอดภัย (ด้วย /// @ts-ignore หรือเอาข้อจำกัด extends DeepPartialAny ออกทั้งหมด) ประเภทที่ได้ก็จะถูกคำนวณอย่างถูกต้องอยู่ดี
ตัวอย่าง
สนามเด็กเล่น TypeScript
type ModifyDeep<A, B extends DeepPartialAny<A>> = {
[K in keyof A | keyof B]:
K extends keyof A
? K extends keyof B
? A[K] extends AnyObject
? B[K] extends AnyObject
? B[K] extends AnyFunction
? B[K]
: ModifyDeep<A[K], B[K]>
: B[K]
: B[K]
: A[K]
: B[K]
}
type AnyObject = Record<string, any>
type AnyFunction = (...args: any[]) => any
type DeepPartialAny<T> = {
/** Makes each property optional and turns each leaf property into any, allowing for type overrides by narrowing any. */
[P in keyof T]?: T[P] extends AnyObject ? DeepPartialAny<T[P]> : any
}
interface Original {
a: {
a: string
b: { a: string }
c: { a: string }
}
b: string
c: { a: string }
}
interface Overrides {
a: {
a: { a: number } // <- overwrite string with object
b: number // <- overwrite object with number
c: { b: number } // <- add new child property
d: number // <- new primitive property
}
d: { a: number } // <- new object property
}
//@ts-ignore // overriding an object with a flat value raises an error although the resulting type is calculated correctly
type ModifiedType = ModifyDeep<Original, Overrides>
//@ts-ignore
interface ModifiedInterface extends ModifyDeep<Original, Overrides> {}
// Try modifying the properties here to prove that the type is working
const t: ModifiedType = {
a: {
a: { a: 0 },
b: 0,
c: { a: '', b: 0},
d: 0,
},
b: '',
c: { a: '' },
d: { a: 0 },
}
