การแทนที่ประเภทคุณสมบัติของอินเตอร์เฟสที่กำหนดในไฟล์ typescript d.ts


140

มีวิธีเปลี่ยนประเภทของคุณสมบัติอินเตอร์เฟสที่กำหนดในไฟล์ *.d.tsใน typecript หรือไม่

ตัวอย่างเช่น: อินเทอร์เฟซในx.d.tsถูกกำหนดเป็น

interface A {
  property: number;
}

ฉันต้องการเปลี่ยนมันในไฟล์ typescript ที่ฉันเขียนถึง

interface A {
  property: Object;
}

หรือแม้กระทั่งสิ่งนี้ก็ใช้ได้

interface B extends A {
  property: Object;
}

แนวทางนี้จะได้ผลหรือไม่? ไม่ได้ผลเมื่อฉันลองใช้ระบบของฉัน แค่ต้องการยืนยันว่าเป็นไปได้หรือไม่?

คำตอบ:


60

คุณไม่สามารถเปลี่ยนประเภทของคุณสมบัติที่มีอยู่

คุณสามารถเพิ่มคุณสมบัติ:

interface A {
    newProperty: any;
}

แต่การเปลี่ยนประเภทที่มีอยู่:

interface A {
    property: any;
}

ผลลัพธ์มีข้อผิดพลาด:

การประกาศตัวแปรตามมาต้องมีชนิดเดียวกัน ตัวแปร 'คุณสมบัติ' ต้องเป็นประเภท 'number' แต่ที่นี่มีประเภท 'any'

แน่นอนคุณสามารถมีอินเทอร์เฟซของคุณเองซึ่งขยายส่วนที่มีอยู่ ในกรณีนั้นคุณสามารถแทนที่ประเภทเฉพาะประเภทที่เข้ากันได้ตัวอย่างเช่น:

interface A {
    x: string | number;
}

interface B extends A {
    x: number;
}

โดยวิธีการที่คุณอาจจะหลีกเลี่ยงการใช้เป็นชนิดที่ใช้แทนประเภทObjectany

ในเอกสารสำหรับ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'.

ด้วย typescript> = 1.1 ในการเขียนทับประเภทของวิธีการโดยการขยายอินเทอร์เฟซคุณต้องรวมวิธีการทั้งหมดจากอินเทอร์เฟซดั้งเดิมมิฉะนั้นคุณจะได้รับข้อผิดพลาดที่ประเภทไม่เข้ากันดูgithub.com/Microsoft/TypeScript/issues/978
jcubic

คุณสามารถละเว้นค่าที่คุณต้องการเขียนทับก่อนจากนั้นกำหนดค่าใหม่เราสามารถทำให้ @ZSkycat เป็นคำตอบในการแก้ปัญหาได้หรือไม่?
zeachco

โหวตลงคะแนนที่อ้างถึง Java เป็น 'ภาษาอื่น ๆ '
wvdz

@wvdz ไม่ใช่ว่าฉันสนใจมากเกี่ยวกับการโหวตลดราคา แต่คุณกำลังพูดถึงอะไร? ไม่มีใครอ้างถึง java ที่ไหน การค้นหาหน้า "java" มีเพียงรายการเดียวที่พบและอยู่ในความคิดเห็นของคุณ
Nitzan Tomer

บางทีฉันอาจจะไม่พอใจนิดหน่อย แต่มันก็ทำให้ฉันรำคาญนิดหน่อยที่คุณพูดว่า "ภาษาอื่น" เมื่อคุณพูดได้เช่นใน Java หรือมีภาษาอื่น ๆ อีกมากมายที่มี Object เป็นคลาสฐานสากล? ฉันรู้จัก C # แต่แน่นอนว่า C # ได้รับแรงบันดาลใจอย่างมากจาก Java
wvdz

265

ฉันใช้วิธีการกรองฟิลด์ก่อนแล้วรวมเข้าด้วยกัน

การอ้างอิงยกเว้นคุณสมบัติจากประเภท

interface A {
    x: string
}

export type B = Omit<A, 'x'> & { x: number };

สำหรับอินเทอร์เฟซ:

interface A {
    x: string
}

interface B extends Omit<A, 'x'> {
  x: number
}

3
เป็นเรื่องดีที่ได้ทราบเรื่องนี้ แต่ปัญหาคือยังไม่แก้ไขสิ่งที่มีอยู่
Freewind

13
นี่คือสิ่งที่ฉันกำลังมองหา เป็นวิธีที่ฉันคาดหวังว่า typescript extendจะทำงานตามค่าเริ่มต้น แต่อนิจจาสิ่งเล็กน้อยนี้Omitแก้ไขทุกอย่าง🙌
Dawson B

1
การขยายอินเทอร์เฟซเป็นสิ่งที่ฉันกำลังมองหาขอบคุณ!
mhodges

1
หมายเหตุ: คุณจะต้องมี typescript 3.5.3 ด้านบนเพื่อใช้สิ่งนี้
วิกซ์สัน

101
type ModifiedType = Modify<OriginalType, {
  a: number;
  b: number;
}>

interface ModifiedInterface extends Modify<OriginalType, {
  a: number;
  b: number;
}> {}

แรงบันดาลใจจากโซลูชันของ ZSkycat extends Omitฉันคิดสิ่งนี้:

type Modify<T, R> = Omit<T, keyof R> & R;

// before typescript@3.5
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


11
นี่เป็นทางออกที่ยอดเยี่ยม
Austin Brunkhorst

1
Noob ที่นี่ แต่คุณเปลี่ยนจากอินเทอร์เฟซเป็นประเภทในตัวอย่างของคุณไม่? หรือไม่มีความแตกต่าง?
Dominic

Niceeeeeeeeee: D
SaMiCoOo

1
@ โดมินิกจุดดีฉันได้อัปเดตคำตอบแล้ว สองอินเทอร์เฟซที่มีชื่อเดียวกันสามารถผสานกันได้ typescriptlang.org/docs/handbook/…
Qwerty

36

การขยายคำตอบของ @ zSkycat เล็กน้อยคุณสามารถสร้างแบบทั่วไปที่ยอมรับอ็อบเจ็กต์สองประเภทและส่งคืนประเภทที่ผสานกับสมาชิกของตัวที่สองที่แทนที่สมาชิกของตัวแรก

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;

1
เจ๋งมาก :-) ฉันเคยทำสิ่งนี้มาก่อนด้วยคุณสมบัติหนึ่งหรือสองอย่างกับ Omit แต่สิ่งนี้เจ๋งกว่ามาก :-) ฉันมักต้องการ 'ขยาย' ประเภทเอนทิตีเซิร์ฟเวอร์และเปลี่ยนบางสิ่งที่จำเป็นหรือเป็นทางเลือกบนไคลเอนต์ .
Simon_Weaver

1
นี่ควรเป็นทางออกที่ได้รับการยอมรับในตอนนี้ วิธีที่สะอาดที่สุดในการ "ขยาย" อินเทอร์เฟซ
manuhortet

14

Omit คุณสมบัติเมื่อขยายอินเทอร์เฟซ:

interface A {
  a: number;
  b: number;
}

interface B extends Omit<A, 'a'> {
  a: boolean;
}

4

สำหรับการกวดขันประเภทของทรัพย์สินที่ง่าย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!

3

มันตลกดีที่ฉันใช้เวลาทั้งวันในการตรวจสอบความเป็นไปได้เพื่อไขคดีเดียวกัน ฉันพบว่าไม่สามารถทำได้ด้วยวิธีนี้:

// a.ts - module
export interface A {
    x: string | any;
}

// b.ts - module
import {A} from './a';

type SomeOtherType = {
  coolStuff: number
}

interface B extends A {
    x: SomeOtherType;
}

สาเหตุโมดูลอาจไม่ทราบเกี่ยวกับประเภทที่มีอยู่ทั้งหมดในแอปพลิเคชันของคุณ และมันค่อนข้างน่าเบื่อพอร์ตทุกอย่างจากทุกที่และการทำโค้ดแบบนี้

export interface A {
    x: A | B | C | D ... Million Types Later
}

คุณต้องกำหนดประเภทในภายหลังเพื่อให้การเติมข้อความอัตโนมัติทำงานได้ดี


ดังนั้นคุณสามารถโกงเล็กน้อย:

// a.ts - module
export interface A {
    x: string;
}

ปล่อยให้บางประเภทเป็นค่าเริ่มต้นซึ่งอนุญาตให้ใช้การเติมข้อความอัตโนมัติได้เมื่อไม่จำเป็นต้องลบล้าง

แล้ว

// b.ts - module
import {A} from './a';

type SomeOtherType = {
  coolStuff: number
}

// @ts-ignore
interface B extends A {
    x: SomeOtherType;
}

ปิดการใช้งานข้อยกเว้นโง่ที่นี่โดยใช้ @ts-ignoreแฟล็กบอกเราว่าเราทำอะไรผิด และสิ่งที่ตลกทุกอย่างทำงานได้ตามที่คาดไว้

ในกรณีของฉันฉันกำลังลดขอบเขตการมองเห็นของประเภทxมันทำให้ฉันทำโค้ดได้เข้มงวดมากขึ้น ตัวอย่างเช่นคุณมีรายการคุณสมบัติ 100 รายการและคุณลดเป็น 10 รายการเพื่อหลีกเลี่ยงสถานการณ์ที่โง่เขลา


3

คำตอบสั้น ๆ สำหรับคนขี้เกียจอย่างฉัน:

type Overrided = Omit<YourInterface, 'overrideField'> & { overrideField: <type> }; 

1

หากมีคนอื่นต้องการยูทิลิตี้ทั่วไปในการทำสิ่งนี้ฉันคิดวิธีแก้ปัญหาต่อไปนี้:

/**
 * Returns object T, but with T[K] overridden to type U.
 * @example
 * type MyObject = { a: number, b: string }
 * OverrideProperty<MyObject, "a", string> // returns { a: string, b: string }
 */
export type OverrideProperty<T, K extends keyof T, U> = Omit<T, K> & { [P in keyof Pick<T, K>]: U };

ฉันต้องการสิ่งนี้เพราะในกรณีของฉันกุญแจสำคัญในการลบล้างคือตัวทั่วไป

หากคุณไม่ได้มีความOmitพร้อมที่ดูไม่รวมทรัพย์สินจากประเภท


1
นี่คือสิ่งที่ฉันกำลังมองหาฉันไม่สามารถขอบคุณมากพอ: D: D: D
dwoodwardgb

@dwoodwardgb ดีใจที่มีประโยชน์สำหรับคนอื่น :-)
Toni

0

หมายเหตุ:ไม่แน่ใจว่าไวยากรณ์ที่ฉันใช้ในคำตอบนี้สามารถใช้ได้หรือไม่เมื่อมีการเขียนคำตอบที่เก่ากว่า แต่ฉันคิดว่านี่เป็นแนวทางที่ดีกว่าในการแก้ตัวอย่างที่กล่าวถึงในคำถามนี้


ฉันมีปัญหาบางอย่างที่เกี่ยวข้องกับหัวข้อนี้ (การเขียนทับคุณสมบัติของอินเทอร์เฟซ) และนี่คือวิธีที่ฉันจัดการ:

  1. ขั้นแรกให้สร้างอินเทอร์เฟซทั่วไปโดยมีประเภทที่เป็นไปได้ที่คุณต้องการใช้

คุณยังสามารถใช้เลือกdefaultค่าสำหรับพารามิเตอร์ทั่วไปดังที่คุณเห็นใน<T extends number | SOME_OBJECT = number>

type SOME_OBJECT = { foo: "bar" }

interface INTERFACE_A <T extends number | SOME_OBJECT = number> {
  property: T;
}
  1. จากนั้นคุณสามารถสร้างประเภทใหม่ตามสัญญานั้นโดยส่งค่าไปยังพารามิเตอร์ทั่วไป (หรือละเว้นและใช้ค่าเริ่มต้น):
type A_NUMBER = INTERFACE_A;                   // USES THE default = number TYPE. SAME AS INTERFACE_A<number>
type A_SOME_OBJECT = INTERFACE_A<SOME_OBJECT>  // MAKES { property: SOME_OBJECT }

และนี่คือผลลัพธ์:

const aNumber: A_NUMBER = {
    property: 111  // THIS EXPECTS A NUMBER
}

const anObject: A_SOME_OBJECT = {
    property: {   // THIS EXPECTS SOME_OBJECT
        foo: "bar"
    }
}

สนามเด็กเล่น typescript


0

ฉันได้สร้างประเภทนี้ขึ้นมาเพื่อให้ฉันสามารถแทนที่อินเทอร์เฟซที่ซ้อนกันได้อย่างง่ายดาย:

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;
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.