Konstruktor w TypeScript to metoda constructor klasy z otypowanymi parametrami. Nie ma adnotacji typu zwracanego: zawsze tworzy instancję klasy.
Zapis constructor(): Product to błąd TS1093 (Type annotation cannot appear on a constructor declaration). Cała reszta działania new to zwykły JavaScript.
Właściwości parametrów
Powyższy wzorzec (zadeklaruj pole, przyjmij parametr, skopiuj go) jest tak częsty, że TypeScript ma na niego skrót. Umieść public, private, protected albo readonly przed parametrem konstruktora, a stanie się on polem:
To jedna z niewielu funkcji TypeScriptu, które generują kod. Kompilator pisze przypisania za ciebie:
class Product {
name;
price;
sku;
constructor(name, price, sku) {
this.name = name;
this.price = price;
this.sku = sku;
}
// ...
}
Ponieważ nie są to tylko usuwane typy, właściwości parametrów nie działają przy wbudowanym w Node usuwaniu typów (node file.ts kończy się błędem ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX), a opcja kompilatora erasableSyntaxOnly zgłasza je jako błąd TS1294. Projekty, które uruchamiają pliki .ts bezpośrednio, wypisują pola ręcznie.
Parametry opcjonalne i domyślne
Parametry konstruktora podlegają tym samym zasadom co parametry funkcji: ? czyni parametr opcjonalnym, wartość domyślna czyni go opcjonalnym i go uzupełnia, a parametry wymagane są na początku.
Właściwości parametrów też mogą mieć wartości domyślne. Typ color wewnątrz klasy to string | undefined, więc kod, który go używa, musi najpierw sprawdzić undefined.
Przy konstruktorach z wieloma opcjami jeden obiekt opcji czyta się lepiej niż długą listę pozycyjną: constructor(opts: { prefix: string; level?: "info" | "debug" }).
Kolejność inicjalizacji pól
Inicjalizatory pól wykonują się przed ciałem konstruktora, a właściwości parametrów są przypisywane na początku ciała konstruktora. Dlatego inicjalizator pola nie może odczytać właściwości parametru. TypeScript to wyłapuje:
index.ts(3,18): error TS2729: Property 'size' is used before its initialization.
index.ts(3,30): error TS2729: Property 'size' is used before its initialization.
W czasie działania inicjalizator zobaczyłby undefined i wyprodukował NaN. Zamiast tego oblicz wartość w ciele konstruktora:
class Grid {
cells: number;
constructor(public size: number) {
this.cells = size * size;
}
}
console.log(new Grid(3).cells); // 9
Przeciążenia konstruktora
Klasa ma dokładnie jedną implementację konstruktora, ale nad nią możesz wypisać kilka sygnatur przeciążeń. Wywołujący widzą tylko przeciążenia; sygnatura implementacji musi być zgodna z nimi wszystkimi.
Przeciążenia stają się trudne do czytania po dwóch albo trzech kształtach. Statyczne metody fabrykujące z opisowymi nazwami (Color.fromHex("#ff8800"), Color.fromRgb(10, 20, 30)) są często czytelniejsze i nie wymagają zawężania typów w jednym ciele.
Wywołanie super w podklasie
Klasa pochodna, która deklaruje własny konstruktor, musi wywołać super(...) z argumentami klasy nadrzędnej i musi to zrobić, zanim dotknie this. Pominięcie wywołania to błąd TS2377 (Constructors for derived classes must contain a 'super' call), a użycie this wcześniej to TS17009 ('super' must be called before accessing 'this' in the constructor of a derived class). Oba odzwierciedlają zasady JavaScriptu w czasie działania.
class Animal {
constructor(public name: string) {}
}
class Dog extends Animal {
tricks: string[];
constructor(name: string, ...tricks: string[]) {
super(name); // must come first
this.tricks = tricks;
}
}
const rex = new Dog("Rex", "sit", "roll");
console.log(rex); // Dog { name: 'Rex', tricks: [ 'sit', 'roll' ] }
Podklasa, która nie dodaje konstruktora, dziedziczy konstruktor klasy nadrzędnej z tymi samymi typami parametrów. Więcej o podklasach na stronie o dziedziczeniu.
Prywatne konstruktory i asynchroniczna konfiguracja
Konstruktor private można wywołać tylko z wnętrza klasy. Na zewnątrz new to błąd TS2673. W ten sposób zmuszasz wywołujących do użycia fabryki, na przykład dla singletona albo dla konfiguracji, która musi być asynchroniczna (konstruktory nie mogą być async).
private jest tu wyłącznie regułą czasu kompilacji. Wyemitowany JavaScript ma zwykły konstruktor, więc zwykły kod JavaScript nadal może wywołać new Connection(...).
Typy konstruktorów
Żeby przyjąć klasę jako wartość, otypuj parametr sygnaturą konstruktora: new (...args) => Instance. typeof MyClass też działa, ale wiąże parametr z sygnaturą konstruktora i składowymi statycznymi tej jednej klasy.
ConstructorParameters i InstanceType wyciągają z typu konstruktora krotkę parametrów i typ instancji; omawia je, razem z innymi pomocnikami funkcji, strona o ReturnType i Parameters.
Najczęściej zadawane pytania
Czym są właściwości parametrów w TypeScript?
Parametr konstruktora z modyfikatorem (public, private, protected lub readonly) deklaruje pole i przypisuje je w jednym kroku. constructor(private name: string) {} to skrót od zadeklarowania private name: string i napisania this.name = name. Kompilator generuje to przypisanie w wyniku.
Czy klasa w TypeScript może mieć kilka konstruktorów?
Nie kilka implementacji. Klasa ma jeden konstruktor, ale nad nim możesz napisać kilka sygnatur przeciążeń, żeby wywołujący widzieli różne listy parametrów. Statyczne metody fabrykujące, takie jak Color.fromHex() i Color.fromRgb(), są często czytelniejsze niż przeciążenia.
Czy konstruktor może być async w TypeScript?
Nie. Konstruktor zawsze zwraca nową instancję, nigdy obietnicę, a async constructor() zostaje odrzucone z błędem TS1089 ('async' modifier cannot appear on a constructor declaration). Użyj prywatnego konstruktora i statycznej metody async create(), która wykonuje pracę asynchroniczną, a potem wywołuje new.
Jak otypować konstruktor klasy jako parametr?
Użyj sygnatury konstruktora: new (name: string) => User albo typeof User dla tej konkretnej klasy. Generyczna fabryka wygląda tak: function make<T>(ctor: new () => T): T { return new ctor(); }. ConstructorParameters<typeof User> daje listę parametrów jako krotkę.
Skąd błąd "'super' must be called before accessing 'this'"?
To błąd TS17009. W klasie, która rozszerza (extends) inną, obiekt tworzy konstruktor klasy nadrzędnej, więc this nie istnieje, dopóki nie wykona się super(...). Przenieś wywołanie super nad każdą linię, która dotyka this.