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?

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

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
}

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


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;

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

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


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!

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 .


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

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.