Menu

Dziedziczenie w TypeScript: extends, super i override

Dziedziczenie klas w TypeScript: extends i super, nadpisywanie metod ze zgodnymi typami, słowo kluczowe override i noImplicitOverride, składowe protected, ponowna deklaracja pól przez declare oraz sytuacje, w których lepszym narzędziem jest implements.

Na tej stronie są działające edytory: edytuj, uruchamiaj i od razu zobacz wynik.

Klasa w TypeScript dziedziczy po innej przez extends. Podklasa dostaje każde pole i każdą metodę rodzica, wywołuje konstruktor rodzica przez super(...) i może nadpisywać metody, a TypeScript sprawdza, czy każde nadpisanie pozostaje zgodne z typami rodzica.

To jest dziedziczenie klas z JavaScriptu (łańcuch prototypów, który za nim stoi, opisuje strona o dziedziczeniu w JavaScript). TypeScript dodaje sprawdzanie typów: Dog da się przypisać wszędzie, gdzie oczekiwany jest Animal, a kompilator weryfikuje nadpisanie i wywołanie super.

Nadpisywanie metod

Metoda podklasy o tej samej nazwie zastępuje metodę rodzica. Jej typ musi dać się przypisać do typu rodzica: te same lub zgodne parametry, ten sam lub węższy typ zwracany. W przeciwnym razie każdy kod, który trzyma Animal, mógłby wywołać metodę z argumentami, których podklasa nie obsłuży.

Zmiana parametru na value: string byłaby błędem TS2416: Property 'format' in type 'CurrencyFormatter' is not assignable to the same property in base type 'Formatter'. Pętla pokazuje dlaczego: wywołuje format z liczbą na każdym elemencie, nie wiedząc, jaką ma podklasę.

Jest jedna luka. Parametry metod są porównywane biwariantnie, więc nadpisanie przyjmujące węższy typ, na przykład format(value: 1 | 2), się kompiluje, choć pętla nadal mogłaby przekazać mu 1234567. Typy zwracane są sprawdzane ściśle: zwrócenie number zamiast string to TS2416.

Słowo kluczowe override i noImplicitOverride

override mówi „ta składowa zastępuje składową klasy bazowej”, a kompilator trzyma cię za słowo. Jeśli klasa bazowa nie ma takiej składowej (literówka albo metoda, którą ktoś przemianował w rodzicu), jest to błąd:

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

Bez override ta literówka się kompiluje, a speak() po cichu zwraca "...". Słowo kluczowe pomaga tylko tam, gdzie je zapisano, więc włącz noImplicitOverride w tsconfig.json. Wtedy każda metoda nadpisująca składową bazową musi to zaznaczyć, a zwykłe speak() w podklasie to błąd TS4114 (This member must have an 'override' modifier because it overrides a member in the base class 'Animal').

{
    "compilerOptions": {
        "strict": true,
        "noImplicitOverride": true
    }
}

noImplicitOverride nie wchodzi w skład strict; trzeba go dodać samodzielnie. override jest usuwane ze skompilowanego JavaScriptu.

Składowe protected w podklasach

Składowe private rodzica są w podklasie niewidoczne. Składowe protected są widoczne i właśnie po to istnieje protected: to API, które klasa nadrzędna oferuje swoim dzieciom.

Pełne reguły, łącznie z tym, dlaczego ani private, ani protected nie są egzekwowane w czasie wykonania, opisuje strona o modyfikatorach dostępu.

Ponowna deklaracja pola z węższym typem

Podklasa często wie, że pole przechowuje coś bardziej szczegółowego, niż mówi rodzic. Ponowna deklaracja jako pet: Dog ma przy nowoczesnych polach klas skutek w czasie wykonania: pole podklasy jest inicjalizowane wartością undefined po powrocie z super(), co kasuje to, co zapisał konstruktor rodzica. Jeśli konstruktor podklasy nie przypisze go ponownie, TypeScript zgłasza to jako TS2612 (Property 'pet' will overwrite the base property in 'Home'). Użyj declare, aby zmienić tylko typ:

declare niczego nie generuje, więc wartość ustawiona przez konstruktor Home przetrwa. To konstruktor przyjmujący tylko Dog sprawia, że węższy typ jest prawdziwy.

implements a extends

Te dwa słowa kluczowe odpowiadają na różne pytania, a klasa może używać obu.

extendsimplements
Działa zjedną klasądowolną liczbą interfejsów (lub aliasów typów obiektowych)
Wnosi kodtak: pola, metody, konstruktornie, tylko sprawdza kształt
Istnieje w czasie wykonaniatak, łańcuch prototypównie, jest usuwane
instanceof działataknie
Typowe użyciewspółdzielenie prawdziwego zachowaniaobietnica kształtu dla reszty kodu

Klasa może rozszerzać tylko jedną klasę (TS1174: Classes can only extend a single class). Aby połączyć zachowanie z kilku miejsc, zaimplementuj kilka interfejsów i współdziel kod przez kompozycję (pole przechowujące obiekt pomocniczy) albo mixiny.

Mixiny

Mixin to funkcja, która przyjmuje klasę i zwraca jej podklasę. Daje to formę wielokrotnego dziedziczenia, która przechodzi sprawdzanie typów:

any[] w Constructor jest wymagane: TypeScript akceptuje jako bazę mixinu tylko klasę, której konstruktor przyjmuje jeden parametr rest typu any[]. Mixiny działają, ale utrudniają czytanie typów i śladów stosu, więc najpierw sięgaj po kompozycję.

Typowe błędy

  • Wywoływanie nadpisywalnej metody z konstruktora bazowego. Konstruktor bazowy działa, zanim pola podklasy zostaną zainicjalizowane, więc nadpisanie, które czyta this.items, widzi undefined, a wywołanie takie jak this.items.push(x) rzuca TypeError w czasie wykonania. TypeScript tego nie wyłapuje.
  • Brak super(...) albo wywołanie go za późno. Błędy TS2377 i TS17009; rodzic musi zbudować obiekt, zanim podklasa dotknie this.
  • Ponowna deklaracja pola rodzica bez declare. Pole podklasy resetuje je do undefined.
  • Głębokie hierarchie. Każdy poziom wiąże dziecko z wnętrzem rodzica. Zwykle wystarczą dwa poziomy; dalej lepiej sięgnąć po kompozycję lub interfejsy.

Najczęściej zadawane pytania

Jak działa dziedziczenie w TypeScript?

Klasa dziedziczy po innej przez extends: class Dog extends Animal. Podklasa dostaje pola i metody rodzica, wywołuje konstruktor rodzica przez super(...) i może nadpisywać metody. TypeScript sprawdza, czy nadpisania pozostają zgodne typowo z rodzicem.

Co robi słowo kluczowe override w TypeScript?

override oznacza metodę lub właściwość, która zastępuje składową klasy bazowej. Jeśli klasa bazowa nie ma składowej o tej nazwie, kompilator zgłasza TS4117, co wyłapuje literówki i składowe przemianowane w rodzicu. Przy włączonym noImplicitOverride każde nadpisanie musi mieć to słowo kluczowe (w przeciwnym razie TS4114).

Jak wywołać metodę klasy nadrzędnej w TypeScript?

Użyj super.methodName(...) wewnątrz metody podklasy. W konstruktorze super(...) wywołuje konstruktor rodzica i musi zostać wykonane przed jakimkolwiek użyciem this.

Czy TypeScript obsługuje wielokrotne dziedziczenie?

Nie. Klasa może rozszerzać tylko jedną klasę (TS1174: Classes can only extend a single class). Może implementować dowolną liczbę interfejsów, a mixiny (funkcje, które przyjmują klasę i zwracają jej podklasę) łączą zachowanie z kilku źródeł.

Czym różni się extends od implements?

extends dziedziczy prawdziwy kod z klasy nadrzędnej. implements tylko sprawdza, czy klasa pasuje do kształtu interfejsu, i nic nie dodaje w czasie wykonania. Klasa może robić jedno i drugie: class Dog extends Animal implements Pet.

Ilustracja języków programowania w Coddy

Ucz się programowania z Coddy

ZACZNIJ