ค้นหาเว็บไซต์

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

บทความที่เกี่ยวข้อง