interface Child extends Parent tworzy nowy interfejs ze wszystkimi składowymi Parent oraz własnymi. Wartości typu potomnego można używać wszędzie tam, gdzie oczekiwany jest rodzic, bo mają wszystkie jego właściwości.
To samo słowo kluczowe pojawia się w TypeScript w czterech miejscach, z powiązanymi, ale różnymi znaczeniami:
| Gdzie | Przykład | Znaczenie |
|---|---|---|
| Interfejs | interface Dog extends Animal {} | dziedziczy składowe rodzica |
| Klasa | class Dog extends Animal {} | dziedziczy pola i implementacje metod (JavaScript w czasie wykonania) |
| Parametr generyczny | <T extends Animal> | ograniczenie: argument typu musi dać się przypisać do Animal |
| Conditional type | T extends string ? A : B | test: czy T da się przypisać do string? |
Rozszerzanie kilku interfejsów
Wypisz kilku rodziców po extends, oddzielając ich przecinkami. Wynik ma składowe ich wszystkich:
interface Identified {
id: number;
}
interface Timestamped {
createdAt: Date;
updatedAt: Date;
}
interface Post extends Identified, Timestamped {
title: string;
}
// Post requires id, createdAt, updatedAt and title
Jeśli dwaj rodzice deklarują tę samą właściwość z typami, które nie są identyczne, interfejs potomny zgłasza błąd TS2320 (Interface 'Post' cannot simultaneously extend types 'A' and 'B'), chyba że sam ponownie zadeklaruje tę właściwość z typem, który da się przypisać do obu.
Rozszerzanie aliasu typu
Alias type nie ma klauzuli extends. Budujesz na nim przez przecięcie, &, które łączy składowe obu stron. Interfejs może też bezpośrednio rozszerzyć alias typu obiektowego, a alias typu może tworzyć przecięcie z interfejsem: oba rodzaje swobodnie się mieszają.
Obie drogi działają tak samo, dopóki właściwości nie wchodzą w konflikt: extends zgłasza konfliktową właściwość przy deklaracji, a & po cichu zamienia ją na never. Porównanie znajdziesz na stronie interface a type, a szczegóły & na stronie o typach przecięcia.
Nadpisywanie typów właściwości
Interfejs potomny może ponownie zadeklarować właściwość rodzica, ale tylko z typem, który da się przypisać do typu rodzica. Zawężenie string do literału albo usunięcie null z unii jest dozwolone. Zmiana typu na coś niezwiązanego nie jest:
index.ts(13,11): error TS2430: Interface 'Label' incorrectly extends interface 'Shape'.
Types of property 'kind' are incompatible.
Type 'number' is not assignable to type 'string'.
Ta reguła istnieje po to, aby Label nadal dało się użyć wszędzie, gdzie oczekiwany jest Shape. Gdyby kind mogło być liczbą, kod napisany dla Shape, który wywołuje shape.kind.toUpperCase(), przestałby działać.
Podmiana właściwości przez Omit
Gdy naprawdę potrzebujesz innego typu dla odziedziczonej właściwości, najpierw usuń ją z rodzica przez Omit, a potem zadeklaruj nową. Typowy przypadek to konwersja danych z API, w których identyfikatory i daty przychodzą jako napisy:
Omit<ApiUser, "id" | "createdAt"> to { name: string }, więc nowe id i createdAt z niczym nie kolidują. Pick, Partial i pozostałe utility types można rozszerzać w ten sam sposób.
extends w klasach
class Dog extends Animal to dziedziczenie klas z JavaScriptu i w odróżnieniu od extends w interfejsach istnieje w czasie wykonania: Dog dostaje konstruktor, pola i implementacje metod Animal, a new Dog(...) instanceof Animal daje true. TypeScript dokłada do tego sprawdzanie typów, na przykład słowo kluczowe override:
Klasa może użyć extends tylko z jedną klasą, ale implements z dowolną liczbą interfejsów. implements niczego nie kopiuje: tylko sprawdza, czy klasa deklaruje to, czego wymaga interfejs. super, składowe chronione i override opisuje strona o dziedziczeniu.
extends w generykach i conditional types
W nawiasach ostrych extends jest ograniczeniem. <T extends { length: number }> przyjmuje każdy argument typu, który ma liczbowe length, i pozwala ciału funkcji używać .length. W conditional type T extends U ? X : Y pyta, czy T da się przypisać do U.
W obu miejscach extends znaczy „da się przypisać do”, czyli tę samą relację, którą sprawdza forma z interfejsami. Ograniczenia opisuje dokładnie strona o ograniczeniach generycznych, a testy na poziomie typów strona o conditional types.
Najczęściej zadawane pytania
Jak rozszerzyć interfejs w TypeScript?
Napisz interface Child extends Parent { ... }. Interfejs potomny ma wszystkie właściwości rodzica oraz te, które sam deklaruje. Aby rozszerzyć kilka interfejsów naraz, oddziel je przecinkami: interface C extends A, B { ... }.
Jak rozszerzyć alias typu w TypeScript?
Alias typu nie może używać extends, więc łączysz typy przecięciem: type Admin = User & { permissions: string[] }. Interfejs może też bezpośrednio rozszerzyć alias typu obiektowego: interface Admin extends User { permissions: string[] }.
Czy przy rozszerzaniu interfejsu można nadpisać typ właściwości?
Tylko typem zgodnym i węższym: kind: "circle" może zastąpić kind: string. Inny typ, na przykład number dla właściwości string, to błąd TS2430 (Interface 'X' incorrectly extends interface 'Y'). Aby zastąpić właściwość niezwiązanym typem, najpierw ją usuń: interface User extends Omit<ApiUser, "id"> { id: number }.
Czym różni się extends od implements?
extends dziedziczy: interfejs dostaje składowe rodzica, a klasa dostaje pola i implementacje metod klasy nadrzędnej. implements tylko sprawdza: klasa implementująca interfejs musi sama zadeklarować każdą składową i nic z interfejsu nie dostaje.
Co znaczy extends w generyku, na przykład <T extends string>?
To ograniczenie, a nie dziedziczenie. T extends string oznacza, że argument typu musi dać się przypisać do string, więc wewnątrz funkcji możesz używać na T metod napisów. W conditional type T extends U ? X : Y jest testem: jeśli T da się przypisać do U, wynikiem jest X, w przeciwnym razie Y.