Jaka jest różnica między „deklaracją klasy” a „interfejsem” w języku TypeScript


116

W TypeScript, podczas tworzenia plików deklaracji źródła .d.ts, co jest lepsze i dlaczego?

declare class Example {
    public Method(): void; 
}

lub

interface Example {
    Method(): void;
}

Różnice, które mogę powiedzieć, są takie, że interfejsy nie mogą mieć metod statycznych, więc musisz użyć do tego klasy. Oba nie produkują żadnego wyjścia JS, więc może to nie ma znaczenia?



Nie wydaje mi się, aby którekolwiek z tych rzeczy naprawdę pomogło w opisaniu, że użyłbyś jednego zamiast drugiego, ponieważ oba mogą osiągnąć to samo bez wyjścia JS.
Chris,

1
Krótko mówiąc: z deklaracją musisz upewnić się, że implementacja klasy istnieje w czasie wykonywania, gdzie z interfejsem nie musisz.
Hakim

Odpowiedzi:


163

interfacesłuży do opisywania kształtu przedmiotu. Nie ma nigdy generowania kodu dla interfejsów - są one wyłącznie artefaktem w systemie typów. Nie zobaczysz różnicy w generowaniu kodu dla klasy w zależności od tego, czy ma ona implementsklauzulę.

declare classjest przeznaczony do opisania istniejącej klasy (zwykle klasy TypeScript, ale nie zawsze), która będzie obecna na zewnątrz (na przykład masz dwa pliki .ts, które kompilują się do dwóch plików .js i oba są dołączane za pomocą scripttagów na stronie internetowej). Jeśli dziedziczysz z classusing extends(niezależnie od tego, czy typem podstawowym był typ a, declare classczy zwykły class), kompilator wygeneruje cały kod, aby podłączyć łańcuch prototypów i konstruktory przekazujące, a co nie.

Jeśli spróbujesz dziedziczyć po declare classinterfejsie, który powinien być interfejsem, wystąpi błąd wykonania, ponieważ wygenerowany kod będzie odnosił się do obiektu bez manifestacji środowiska wykonawczego.

I odwrotnie, jeśli po prostu masz implementinterfejs, który powinien być interfejsem declare class, będziesz musiał sam ponownie zaimplementować wszystkie elementy członkowskie i nie będziesz korzystać z ponownego wykorzystania kodu z przyszłej klasy bazowej i funkcji które sprawdzą łańcuch prototypów w czasie wykonywania, odrzucą twój obiekt jako nie będący w rzeczywistości instancją klasy bazowej.

Aby uzyskać naprawdę nerdy, jeśli masz C ++ tła, można z grubsza myśleć interfacejak typedefi declare classjako externdeklaracja konstruktora, który ściśle brakuje definicji w tej jednostce kompilacji.

Od strony czystej konsumpcji (pisanie imperatywnego kodu, a nie dodawanie nowych typów) jedyną różnicą między interfacei declare classjest to, że nie możesz newmieć interfejsu. Jeśli jednak zamierzasz extend/ implementjeden z tych typów w nowym class, koniecznie musisz wybrać poprawnie pomiędzy interfacei declare class. Tylko jeden z nich będzie działać.

Dwie zasady, które będą Ci dobrze służyć:

  • Czy nazwa typu jest zgodna z funkcją konstruktora (czymś, z czym można ją wywołać new), która jest faktycznie obecna w czasie wykonywania (np. DateJest, ale JQueryStaticnie jest)? Jeśli nie , zdecydowanie chceszinterface
  • Czy mam do czynienia ze skompilowaną klasą z innego pliku TypeScript, czy z czymś wystarczająco podobnym? Jeśli tak , użyjdeclare class

Właściwie możesz nowy interfejs w maszynopisie. Jedynym ograniczeniem jest dziedziczenie.
Oleg Mihailik

3
Nie można wywołać newoperatora dla typu interfejsu. Jednak interfejsy mogą mieć sygnatury konstrukcji, co oznacza, że ​​można wywołać newoperator na wartości typu interfejsu. Jest to zupełnie inne niż sposób classdziałania, w którym sygnatura konstrukcji znajduje się na samej nazwie typu, a nie na wyrażeniu tego typu.
Ryan Cavanaugh

Jeśli pójdziesz drogą dodawania konstruktora do interfejsu, powinien on być JEDYNYM członkiem interfejsu, z wyjątkiem „statyki” klasy. Nie łącz interfejsu funkcji konstruktora z interfejsem konstruowanego obiektu. Jeśli to zrobisz, system typów dopuszcza takie głupoty jak: nowy (nowy x ()), gdzie x: Interfejs.
Jeremy Bell

24

Możesz zaimplementować interfejs:

class MyClass implements Example {
    Method() {

    }
}

Podczas gdy declare classskładnia jest naprawdę przeznaczona do dodawania definicji typu dla zewnętrznego kodu, który nie jest napisany w TypeScript - więc implementacja jest „gdzie indziej”.


Więc sugerujesz, że deklaracja klasy powinna być używana do opisu kodu, który nie został napisany w TypeScript? Zakładałbym, że tak jest, ale w wpisanym pliku jquery.d.ts JQueryStatic jest interfejsem zaimplementowanym przez: deklarację var $: JQueryStatic Chciałbym, żeby to była deklaracja klasy $ {public static ...}
Chris

Jedynym powodem, dla którego przychodzi mi do głowy, byłoby to, że nie chcesz, aby ludzie rozszerzali klasę - użycie interfejsu oznacza, że ​​będziesz musiał dostarczyć całą implementację.
Fenton,

Ma sens. Może to był powód.
Chris,

OK, więc próbuję wskazać deklaracje w innej bibliotece JS, więc zdecydowanie wymagam deklaracji. Kod korzysta ze statyki w niektórych funkcjach (klasach) bibliotecznych - jak dotąd nie widziałem statycznej właściwości lub metody wyrażonej w pliku deklaracji. Aha, i czy powinienem używać przestrzeni nazw czy modułu?
wydarzenie jenson-button

13

Mówiąc prościej, declarejest używany w .ts/ d.tsfiles, aby powiedzieć kompilatorowi, że powinniśmy oczekiwać słowa kluczowego, które będziemy declaringistnieć w tym środowisku, nawet jeśli nie jest zdefiniowane w obecnym pliku. Pozwoli nam to zachować bezpieczeństwo typów podczas używania zadeklarowanego obiektu, ponieważ kompilator Typescript wie teraz, że inny składnik może udostępniać tę zmienną.


6

Różnica między declareiw interfaceTS:

ogłosić:

declare class Example {
    public Method(): void; 
}

W powyższym kodzie declarepozwala kompilatorowi TS wiedzieć, że gdzieś Examplezostała zadeklarowana klasa . Nie oznacza to, że klasa jest magicznie włączona. Ty jako programista jesteś odpowiedzialny za posiadanie klasy dostępnej, kiedy ją deklarujesz (za pomocą declaresłowa kluczowego).

berło:

interface Example {
    Method(): void;
}

Jest interfaceto konstrukcja wirtualna, która istnieje tylko w maszynopisie. Kompilator maszynopisu używa go wyłącznie do sprawdzania typów. Kiedy kod jest kompilowany do javascript, cała konstrukcja zostanie usunięta. Kompilator maszynopisu używa interfejsów w celu sprawdzenia, czy obiekty mają odpowiednią strukturę.

Na przykład, gdy mamy następujący interfejs:

interface test {
  foo: number,
  bar: string,
}

Obiekty, które definiujemy, które mają ten typ interfejsu, muszą dokładnie pasować do interfejsu:

// perfect match has all the properties with the right types, TS compiler will not complain.
  const obj1: test = {   
    foo: 5,
    bar: 'hey',
  }
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.