Zastępowanie typu właściwości interfejsu zdefiniowanego w pliku Typescript d.ts

128

Czy istnieje sposób na zmianę typu właściwości interfejsu zdefiniowanej *.d.tsw skrypcie?

na przykład: Interfejs w x.d.tsjest zdefiniowany jako

interface A {
  property: number;
}

Chcę to zmienić w plikach maszynopisu, do których piszę

interface A {
  property: Object;
}

albo nawet to zadziała

interface B extends A {
  property: Object;
}

Czy to podejście zadziała? Nie działało, kiedy próbowałem na moim systemie. Chcesz tylko potwierdzić, czy to w ogóle możliwe?

Abdul23
źródło

Odpowiedzi:

56

Nie możesz zmienić typu istniejącej usługi.

Możesz dodać usługę:

interface A {
    newProperty: any;
}

Ale zmiana typu istniejącego:

interface A {
    property: any;
}

Powoduje błąd:

Kolejne deklaracje zmiennych muszą mieć ten sam typ. Zmienna „właściwość” musi być typu „liczba”, ale tutaj ma typ „dowolna”

Możesz oczywiście mieć własny interfejs, który rozszerzy istniejący. W takim przypadku możesz zastąpić typ tylko zgodnym typem, na przykład:

interface A {
    x: string | number;
}

interface B extends A {
    x: number;
}

Nawiasem mówiąc, prawdopodobnie powinieneś unikać używania Objectjako typu, zamiast tego użyj typu any.

W dokumentach tego anytypu stwierdza się:

Dowolny typ to potężny sposób pracy z istniejącym JavaScriptem, umożliwiający stopniowe włączanie i wyłączanie sprawdzania typów podczas kompilacji. Można się spodziewać, że Object będzie odgrywał podobną rolę, jak w innych językach. Ale zmienne typu Object pozwalają tylko na przypisanie im dowolnej wartości - nie można wywołać na nich dowolnych metod, nawet tych, które faktycznie istnieją :

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'.
Nitzan Tomer
źródło
Z Typescript> = 1.1, aby nadpisać typ metod poprzez rozszerzenie interfejsu, musisz uwzględnić wszystkie metody z oryginalnego interfejsu, w przeciwnym razie pojawi się błąd, że typy są niezgodne, patrz github.com/Microsoft/TypeScript/issues/978
jcubic
możesz najpierw pominąć wartości, które chcesz nadpisać, a następnie przedefiniować je, czy możemy sprawić, by odpowiedź @ZSkycat była rozwiązaniem rozwiązującym?
zeachco
Głos przeciw określaniu języka Java jako „innych języków”
wvdz
@wvdz nie to, żebym przejmował się głosem przeciw, ale o czym ty mówisz? gdzie ktoś nawet odniósł się do javy? wyszukanie na stronie hasła „java” ma tylko jedno znalezisko i jest ono w Twoim komentarzu.
Nitzan Tomer
Może byłem trochę zrzędliwy, ale trochę mnie zirytowało, że powiedziałeś „inne języki”, podczas gdy mogłeś po prostu powiedzieć, jak w Javie. A może naprawdę jest wiele innych języków, dla których Object jest uniwersalną klasą bazową? Znam C #, ale oczywiście C # był mocno zainspirowany Javą.
wvdz
232

Używam metody, która najpierw filtruje pola, a następnie je łączy.

odwołanie Wyklucz właściwość z typu

interface A {
    x: string
}

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

dla interfejsu:

interface A {
    x: string
}

interface B extends Omit<A, 'x'> {
  x: number
}
ZSkycat
źródło
3
Dobrze to wiedzieć. Ale problem polega na tym, że nadal nie modyfikuje istniejącego.
Freewind
10
To było dokładnie to, czego szukałem. Tak właśnie oczekiwałem, że maszynopis extendbędzie działał domyślnie, ale niestety ten mały Omitnaprawia wszystko 🙌
Dawson B,
1
Rozszerzenie interfejsu było dokładnie tym, czego szukałem, dzięki!
mhodges
1
Uwaga: aby z tego skorzystać, będziesz potrzebować powyższego maszynopisu 3.5.3.
Vixson
93
type ModifiedType = Modify<OriginalType, {
  a: number;
  b: number;
}>

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

Zainspirowany rozwiązaniem ZSkycat extends Omit wymyśliłem to:

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

// before [email protected]
type Modify<T, R> = Pick<T, Exclude<keyof T, keyof R>> & R

Przykład:

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

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

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

Idąc krok po kroku:

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

Typy narzędzi TypeScript

Qwerty
źródło
9
To świetne rozwiązanie.
Austin Brunkhorst
1
Noob tutaj, ale zmieniłeś interfejs na typ w swoim przykładzie, nie? A może nie ma różnicy?
Dominic
Niceeeeeeeeee: D
SaMiCoOo
1
@Dominic Słuszna uwaga, zaktualizowałem odpowiedź. Dwa interfejsy o tej samej nazwie mogą się łączyć. typescriptlang.org/docs/handbook/…
Qwerty
35

Rozszerzając nieco odpowiedź @ zSkycat, możesz utworzyć rodzaj generyczny, który akceptuje dwa typy obiektów i zwraca scalony typ, w którym składowe drugiego zastępują elementy pierwszego.

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;
ryanjduffy
źródło
1
Bardzo fajnie :-) Robiłem to już wcześniej z jedną lub dwiema właściwościami z Omit, ale jest to o wiele fajniejsze :-) Często chcę „rozszerzyć” typ jednostki serwera i zmienić niektóre rzeczy, które są wymagane lub opcjonalne na kliencie .
Simon_Weaver
1
To powinno być teraz akceptowane rozwiązanie. Najczystszy sposób na „rozszerzenie” interfejsu.
manuhortet
12

Omit właściwość przy rozszerzaniu interfejsu:

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

interface B extends Omit<A, 'a'> {
  a: boolean;
}
JmJ
źródło
3

To zabawne, że spędzam cały dzień na badaniu możliwości rozwiązania tej samej sprawy. Okazało się, że nie można tego zrobić:

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

Przyczyna Moduł może nie wiedzieć o wszystkich typach dostępnych w twojej aplikacji. I jest to dość nudne przenoszenie wszystkiego z dowolnego miejsca i robienie kodu w ten sposób.

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

Musisz później zdefiniować typ, aby autouzupełnianie działało dobrze.


Możesz więc trochę oszukać:

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

Pozostawiono domyślnie jakiś typ, który zezwala na autouzupełnianie, gdy nadpisania nie są wymagane.

Następnie

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

type SomeOtherType = {
  coolStuff: number
}

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

Wyłącz głupi wyjątek, używając @ts-ignoreflagi, mówiąc nam, że robimy coś źle. I zabawne, wszystko działa zgodnie z oczekiwaniami.

W moim przypadku ograniczam zakres wizji typu x, pozwala mi to robić kod w bardziej restrykcyjny sposób. Na przykład masz listę 100 nieruchomości i zmniejszasz ją do 10, aby uniknąć głupich sytuacji

Egor Malkevich
źródło
3

Aby zawęzić rodzaj nieruchomości, proste extenddziała idealnie, jak w odpowiedzi Nitzana :

interface A {
    x: string | number;
}

interface B extends A {
    x: number;
}

Dla poszerzenia lub ogólnie nadrzędnymi typu można zrobić rozwiązanie Zskycat za :

interface A {
    x: string
}

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

Jeśli jednak twój interfejs Arozszerza ogólny interfejs, Apodczas używania utracisz niestandardowe typy pozostałych właściwości Omit.

na przykład

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! 

Powodem jest to, że Omitwewnętrznie przechodzą tylko przez Exclude<keyof A, 'x'>klucze, które będą generalne string | numberw naszym przypadku. Tak więc Bstałoby się {x: number; }i akceptuje każdą dodatkową właściwość typu number | string | boolean.


Aby to naprawić, wymyśliłem inny OverridePropstyp narzędzia:

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

Przykład:

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!
Aidin
źródło
1

Jeśli ktoś potrzebuje do tego ogólnego typu narzędzia, wymyśliłem następujące rozwiązanie:

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

Potrzebowałem tego, ponieważ w moim przypadku klucz do zastąpienia był sam w sobie generyczny.

Jeśli nie masz jeszcze Omitgotowego, zobacz Wykluczanie właściwości z typu .

Toni
źródło
1
Właśnie tego szukałem, nie mogę ci wystarczająco podziękować: D: D: D
dwoodwardgb
@dwoodwardgb cieszę się, że było to przydatne dla kogoś innego :-)
Toni
0

UWAGA: Nie jestem pewien, czy składnia, której używam w tej odpowiedzi, była dostępna, gdy pisano starsze odpowiedzi, ale myślę, że jest to lepsze podejście do rozwiązania przykładu wspomnianego w tym pytaniu.


Miałem kilka problemów związanych z tym tematem (nadpisywanie właściwości interfejsu) i tak sobie z tym radzę:

  1. Najpierw utwórz ogólny interfejs z możliwymi typami, których chcesz używać.

Możesz nawet użyć opcji wybierz defaultwartość dla parametru ogólnego, jak widać w<T extends number | SOME_OBJECT = number>

type SOME_OBJECT = { foo: "bar" }

interface INTERFACE_A <T extends number | SOME_OBJECT = number> {
  property: T;
}
  1. Następnie możesz utworzyć nowe typy na podstawie tego kontraktu, przekazując wartość do parametru generycznego (lub pomiń go i użyj wartości domyślnej):
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 }

A oto wynik:

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

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

Maszynopisowy plac zabaw

cbdeveloper
źródło