Eine TypeScript-Klasse erbt mit extends von einer anderen. Die Unterklasse bekommt jedes Feld und jede Methode der Elternklasse, ruft den Elternkonstruktor mit super(...) auf und kann Methoden überschreiben, und TypeScript prüft, dass jede Überschreibung zu den Typen der Elternklasse kompatibel bleibt.
Das ist Klassenvererbung von JavaScript (die Prototypkette dahinter steht unter Vererbung in JavaScript). TypeScript ergänzt die Typprüfungen: Ein Dog ist überall zuweisbar, wo ein Animal erwartet wird, und der Compiler prüft die Überschreibung und den Aufruf von super.
Methoden überschreiben
Eine gleichnamige Methode der Unterklasse ersetzt die der Elternklasse. Ihr Typ muss dem der Elternklasse zuweisbar sein: gleiche oder kompatible Parameter, gleicher oder engerer Rückgabetyp. Sonst könnte jeder Code, der ein Animal hält, die Methode mit Argumenten aufrufen, die die Unterklasse nicht verarbeiten kann.
Den Parameter auf value: string zu ändern wäre der Fehler TS2416: Property 'format' in type 'CurrencyFormatter' is not assignable to the same property in base type 'Formatter'. Die Schleife zeigt, warum: Sie ruft format bei jedem Element mit einer Zahl auf, ohne zu wissen, welche Unterklasse sie vor sich hat.
Es gibt eine Lücke. Methodenparameter werden bivariant verglichen, also kompiliert eine Überschreibung, die einen engeren Typ akzeptiert, etwa format(value: 1 | 2), obwohl die Schleife ihr weiterhin 1234567 übergeben könnte. Rückgabetypen werden streng geprüft: number statt string zurückzugeben ist TS2416.
Das Schlüsselwort override und noImplicitOverride
override sagt: Dieser Member ersetzt einen der Basisklasse, und der Compiler nimmt dich beim Wort. Hat die Basis keinen solchen Member (ein Tippfehler oder eine Methode, die jemand in der Elternklasse umbenannt hat), ist das ein Fehler:
index.ts(9,14): error TS4117: This member cannot have an 'override' modifier because it is not declared in the base class 'Animal'. Did you mean 'speak'?
Ohne override kompiliert dieser Tippfehler, und speak() gibt stillschweigend "..." zurück. Das Schlüsselwort hilft nur dort, wo es steht, schalte also noImplicitOverride in tsconfig.json ein. Dann muss jede Methode, die einen Basismember überschreibt, das angeben, und ein einfaches speak() in einer Unterklasse ist der Fehler TS4114 (This member must have an 'override' modifier because it overrides a member in the base class 'Animal').
{
"compilerOptions": {
"strict": true,
"noImplicitOverride": true
}
}
noImplicitOverride gehört nicht zu strict; du musst es selbst hinzufügen. override wird aus dem kompilierten JavaScript entfernt.
protected Member in Unterklassen
private Member der Elternklasse sind in der Unterklasse unsichtbar. protected Member sind sichtbar, und genau deshalb gibt es protected: Es ist die API, die eine Elternklasse ihren Kindern anbietet.
Die vollständigen Regeln stehen unter Zugriffsmodifikatoren, auch warum weder private noch protected zur Laufzeit durchgesetzt wird.
Ein Feld mit engerem Typ neu deklarieren
Eine Unterklasse weiß oft, dass ein Feld etwas Spezifischeres enthält, als die Elternklasse sagt. Es als pet: Dog neu zu deklarieren hat mit modernen Klassenfeldern eine Wirkung zur Laufzeit: Das Feld der Unterklasse wird nach dem Ende von super() mit undefined initialisiert und löscht, was der Elternkonstruktor gespeichert hat. Weist der Konstruktor der Unterklasse es nicht erneut zu, meldet TypeScript das als TS2612 (Property 'pet' will overwrite the base property in 'Home'). Nimm declare, um nur den Typ zu ändern:
declare erzeugt nichts, also bleibt der vom Konstruktor von Home gesetzte Wert erhalten. Der Konstruktor, der nur ein Dog akzeptiert, macht den engeren Typ wahr.
implements vs extends
Die beiden Schlüsselwörter beantworten unterschiedliche Fragen, und eine Klasse kann beide verwenden.
extends | implements | |
|---|---|---|
| Funktioniert mit | einer Klasse | beliebig vielen Interfaces (oder Typaliassen von Objekttypen) |
| Bringt Code mit | ja: Felder, Methoden, Konstruktor | nein, prüft nur die Form |
| Existiert zur Laufzeit | ja, die Prototypkette | nein, entfernt |
instanceof funktioniert | ja | nein |
| Typische Verwendung | echtes Verhalten teilen | dem restlichen Code eine Form versprechen |
Eine Klasse kann nur eine Klasse erweitern (TS1174: Classes can only extend a single class). Um Verhalten aus mehreren Quellen zu kombinieren, implementiere mehrere Interfaces und teile Code über Komposition (ein Feld mit einem Hilfsobjekt) oder Mixins.
Mixins
Ein Mixin ist eine Funktion, die eine Klasse nimmt und eine Unterklasse davon zurückgibt. Das ergibt eine Form von Mehrfachvererbung, die die Typprüfung besteht:
Das any[] in Constructor ist Pflicht: TypeScript akzeptiert als Basis eines Mixins nur einen Konstruktor, der einen einzigen Rest-Parameter vom Typ any[] nimmt. Mixins funktionieren, machen Typen und Stacktraces aber schwerer lesbar, greife also zuerst zur Komposition.
Häufige Fehler
- Eine überschreibbare Methode im Basiskonstruktor aufrufen. Der Basiskonstruktor läuft, bevor die Felder der Unterklasse initialisiert sind, also sieht eine Überschreibung, die
this.itemsliest,undefined, und ein Aufruf wiethis.items.push(x)wirft zur Laufzeit einenTypeError. TypeScript findet das nicht. super(...)vergessen oder zu spät aufrufen. FehlerTS2377undTS17009; die Elternklasse muss das Objekt bauen, bevor die Unterklassethisberührt.- Ein Feld der Elternklasse ohne
declareneu deklarieren. Das Feld der Unterklasse setzt es aufundefinedzurück. - Tiefe Hierarchien. Jede Ebene koppelt das Kind an die Interna der Elternklasse. Zwei Ebenen reichen meist; darüber hinaus sind Komposition oder Interfaces besser.
Häufig gestellte Fragen
Wie funktioniert Vererbung in TypeScript?
Eine Klasse erbt mit extends von einer anderen: class Dog extends Animal. Die Unterklasse bekommt die Felder und Methoden der Elternklasse, ruft den Elternkonstruktor mit super(...) auf und kann Methoden überschreiben. TypeScript prüft, dass Überschreibungen typkompatibel zur Elternklasse bleiben.
Was macht das Schlüsselwort override in TypeScript?
override markiert eine Methode oder Eigenschaft, die eine der Basisklasse ersetzt. Hat die Basisklasse keinen Member dieses Namens, meldet der Compiler TS4117, was Tippfehler und in der Elternklasse umbenannte Member abfängt. Mit aktivem noImplicitOverride muss jede Überschreibung das Schlüsselwort tragen (sonst TS4114).
Wie rufe ich in TypeScript eine Methode der Elternklasse auf?
Mit super.methodName(...) in der Methode der Unterklasse. Im Konstruktor ruft super(...) den Elternkonstruktor auf und muss vor jeder Verwendung von this laufen.
Unterstützt TypeScript Mehrfachvererbung?
Nein. Eine Klasse kann nur eine Klasse erweitern (TS1174: Classes can only extend a single class). Sie kann beliebig viele Interfaces implementieren, und Mixins (Funktionen, die eine Klasse nehmen und eine Unterklasse zurückgeben) kombinieren Verhalten aus mehreren Quellen.
Was ist der Unterschied zwischen extends und implements?
extends erbt echten Code von einer Elternklasse. implements prüft nur, dass eine Klasse zur Form eines Interfaces passt, und fügt zur Laufzeit nichts hinzu. Eine Klasse kann beides: class Dog extends Animal implements Pet.