Ghi đè loại thuộc tính giao diện được xác định trong tệp d.ts Typescript


127

Có cách nào để thay đổi loại thuộc tính giao diện được xác định trong một *.d.tstrong bảng chữ không?

ví dụ: Một giao diện trong x.d.tsđược định nghĩa là

interface A {
  property: number;
}

Tôi muốn thay đổi nó trong các tệp sắp chữ mà tôi viết vào

interface A {
  property: Object;
}

hoặc thậm chí điều này sẽ hoạt động

interface B extends A {
  property: Object;
}

Cách tiếp cận này sẽ hoạt động? Nó không hoạt động khi tôi thử trên hệ thống của mình. Chỉ muốn xác nhận xem nó có khả thi không?

Câu trả lời:


56

Bạn không thể thay đổi loại thuộc tính hiện có.

Bạn có thể thêm một thuộc tính:

interface A {
    newProperty: any;
}

Nhưng thay đổi một loại hiện có:

interface A {
    property: any;
}

Kết quả là một lỗi:

Các khai báo biến tiếp theo phải có cùng kiểu. 'Thuộc tính' biến phải thuộc loại 'số', nhưng ở đây có loại 'bất kỳ'

Tất nhiên, bạn có thể có giao diện của riêng mình, mở rộng giao diện hiện có. Trong trường hợp đó, bạn có thể ghi đè một loại chỉ thành một loại tương thích, ví dụ:

interface A {
    x: string | number;
}

interface B extends A {
    x: number;
}

Nhân tiện, có lẽ bạn nên tránh sử dụng Objectnhư một loại, thay vào đó hãy sử dụng loại any.

Trong tài liệu cho anyloại nó nói:

Loại bất kỳ là một cách hiệu quả để làm việc với JavaScript hiện có, cho phép bạn dần dần chọn tham gia và từ chối kiểm tra loại trong quá trình biên dịch. Bạn có thể mong đợi Đối tượng đóng một vai trò tương tự, giống như trong các ngôn ngữ khác. Nhưng các biến kiểu Đối tượng chỉ cho phép bạn gán bất kỳ giá trị nào cho chúng - bạn không thể gọi các phương thức tùy ý trên chúng, ngay cả những phương thức thực sự tồn tại :

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'.

Với Typecript> = 1.1 để ghi đè kiểu của các phương thức bằng cách mở rộng giao diện, bạn cần bao gồm tất cả các phương thức từ giao diện gốc, nếu không bạn sẽ gặp lỗi rằng các kiểu không tương thích, hãy xem github.com/Microsoft/TypeScript/issues/978
jcubic

bạn có thể Bỏ qua các giá trị mà bạn muốn ghi đè trước rồi xác định lại chúng, chúng ta có thể làm cho câu trả lời của @ZSkycat trở thành giải pháp không?
zeachco

Phản đối vì coi Java là 'ngôn ngữ khác'
wvdz

@wvdz không phải tôi quan tâm nhiều đến downvote, nhưng bạn đang nói về cái gì vậy? nơi nào đã có ai nói đến java? tìm kiếm trang cho "java" chỉ có một tìm thấy và nó nằm trong nhận xét của bạn.
Nitzan Tomer

Có lẽ tôi hơi khó chịu nhưng tôi hơi khó chịu khi bạn nói "ngôn ngữ khác" trong khi bạn có thể nói, như trong Java. Hay thực sự có rất nhiều ngôn ngữ khác có Object là lớp cơ sở phổ quát? Tôi biết về C #, nhưng tất nhiên C # được lấy cảm hứng từ Java.
wvdz

230

Tôi sử dụng một phương pháp đầu tiên lọc các trường và sau đó kết hợp chúng.

tham chiếu Loại trừ thuộc tính khỏi loại

interface A {
    x: string
}

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

cho giao diện:

interface A {
    x: string
}

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

3
Thật tuyệt khi biết điều này. Nhưng vấn đề là nó vẫn không sửa đổi cái hiện có.
Freewind

10
Đây chính xác là những gì tôi đang tìm kiếm. Đó là cách tôi mong đợi các nguyên cảo extendđể làm việc theo mặc định, nhưng than ôi, điều này ít Omitsửa chữa mọi thứ 🙌
Dawson B

1
Mở rộng giao diện chính xác là những gì tôi đang tìm kiếm, cảm ơn!
mhodges

1
Lưu ý: Bạn sẽ cần bản định kiểu 3.5.3 ở trên để sử dụng.
Vixson

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

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

Lấy cảm hứng từ giải pháp của ZSkycat extends Omit , tôi đã nghĩ ra điều này:

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

Thí dụ:

interface OriginalInterface {
  a: string;
  b: boolean;
  c: number;
}

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

// ModifiedType = { a: number; b: number; c: number; }

Đi từng bước:

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

Các loại tiện ích TypeScript


9
Đây là một giải pháp tuyệt vời.
Austin Brunkhorst

1
Noob ở đây nhưng bạn đang thay đổi từ một giao diện sang một loại trong ví dụ của bạn không? Hay là không có sự khác biệt?
Dominic

Niceeeeeeeeee: D
SaMiCoOo

1
@Dominic Điểm tốt, tôi đã cập nhật câu trả lời. Hai Giao diện có cùng tên có thể hợp nhất. typecriptlang.org/docs/handbook/…
Qwerty

35

Mở rộng câu trả lời của @ zSkycat một chút, bạn có thể tạo một đối tượng chung chấp nhận hai loại đối tượng và trả về một loại hợp nhất với các thành viên của thứ hai ghi đè các thành viên của thứ nhất.

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
Rất tuyệt :-) Tôi đã làm điều này trước đây với một hoặc hai thuộc tính với Omit, nhưng điều này thú vị hơn nhiều :-) Tôi thường muốn 'mở rộng' một loại thực thể máy chủ và thay đổi một số thứ được yêu cầu hoặc tùy chọn trên máy khách .
Simon_Weaver

1
Đây nên là giải pháp được chấp nhận ngay bây giờ. Cách sạch nhất để "mở rộng" giao diện.
manuhortet

11

Omit thuộc tính khi mở rộng giao diện:

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

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

3

Thật buồn cười khi tôi dành cả ngày để điều tra khả năng giải quyết trường hợp tương tự. Tôi thấy rằng không thể làm theo cách này:

// 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;
}

Nguyên nhân Một mô-đun có thể không biết về tất cả các loại có sẵn trong ứng dụng của bạn. Và nó khá nhàm chán khi chuyển mọi thứ từ mọi nơi và làm mã như thế này.

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

Bạn phải xác định loại sau này để tính năng tự động hoàn thành hoạt động tốt.


Vì vậy, bạn có thể gian lận một chút:

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

Để lại một số loại theo mặc định, cho phép tự động hoàn thành hoạt động, khi không cần ghi đè.

Sau đó

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

type SomeOtherType = {
  coolStuff: number
}

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

Tắt ngoại lệ ngu ngốc ở đây bằng cách sử dụng @ts-ignore cờ, cho biết chúng tôi đang làm sai điều gì đó. Và điều buồn cười là mọi thứ hoạt động như mong đợi.

Trong trường hợp của tôi, tôi đang giảm tầm nhìn phạm vi của loại x, nó cho phép tôi làm mã nghiêm ngặt hơn. Ví dụ: bạn có danh sách 100 thuộc tính và bạn giảm nó xuống còn 10, để tránh những trường hợp ngớ ngẩn


3

Để thu hẹp loại thuộc tính, đơn giản extendhoạt động hoàn hảo, như trong câu trả lời của Nitzan :

interface A {
    x: string | number;
}

interface B extends A {
    x: number;
}

Để mở rộng hoặc thường ghi đè loại, bạn có thể thực hiện giải pháp của Zskycat :

interface A {
    x: string
}

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

Nhưng, nếu giao diện của bạn Ađang mở rộng giao diện chung, bạn sẽ mất các loại tùy chỉnh của Acác thuộc tính còn lại khi sử dụng Omit.

ví dụ

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! 

Lý do là, Omitnội bộ chỉ xem xét Exclude<keyof A, 'x'>các khóa sẽ là chung string | numbertrong trường hợp của chúng tôi. Vì vậy, Bsẽ trở thành {x: number; }và chấp nhận bất kỳ thuộc tính bổ sung nào với loại number | string | boolean.


Để khắc phục điều đó, tôi đã nghĩ ra một OverridePropsloại tiện ích khác như sau:

type OverrideProps<M, N> = { [P in keyof M]: P extends keyof N ? N[P] : M[P] };

Thí dụ:

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!

1

Nếu ai đó cần một loại tiện ích chung để thực hiện việc này, tôi đã đưa ra giải pháp sau:

/**
 * 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 };

Tôi cần điều này bởi vì trong trường hợp của tôi, chìa khóa để ghi đè là bản thân nó chung chung.

Nếu bạn chưa Omitsẵn sàng, hãy xem Loại trừ thuộc tính khỏi loại .


1
Đây chính xác là những gì tôi đang tìm kiếm, tôi không thể cảm ơn bạn đủ: D: D: D
dwoodwardgb

@dwoodwardgb rất vui vì nó hữu ích cho người khác :-)
Toni

0

LƯU Ý: Không chắc liệu cú pháp tôi đang sử dụng trong câu trả lời này có khả dụng khi các câu trả lời cũ hơn được viết hay không, nhưng tôi nghĩ rằng đây là một cách tiếp cận tốt hơn về cách giải ví dụ được đề cập trong câu hỏi này.


Tôi đã gặp một số vấn đề liên quan đến chủ đề này (ghi đè thuộc tính giao diện) và đây là cách tôi xử lý nó:

  1. Đầu tiên, hãy tạo một giao diện chung, với các loại có thể bạn muốn sử dụng.

Bạn thậm chí có thể sử dụng chọn defaultgiá trị cho thông số chung như bạn có thể thấy trong<T extends number | SOME_OBJECT = number>

type SOME_OBJECT = { foo: "bar" }

interface INTERFACE_A <T extends number | SOME_OBJECT = number> {
  property: T;
}
  1. Sau đó, bạn có thể tạo các loại mới dựa trên hợp đồng đó, bằng cách chuyển một giá trị cho tham số chung (hoặc bỏ qua và sử dụng giá trị mặc định):
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 }

Và đây là kết quả:

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

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

Sân chơi đánh chữ

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.