Конструктор в TypeScript это метод constructor класса с типизированными параметрами. У него нет аннотации типа возврата: он всегда создаёт экземпляр класса.
Запись constructor(): Product это ошибка TS1093 (Type annotation cannot appear on a constructor declaration). Всё остальное в работе new это обычный JavaScript.
Свойства-параметры
Приём выше (объявить поле, принять параметр, скопировать его) настолько распространён, что в TypeScript есть сокращение. Поставьте public, private, protected или readonly перед параметром конструктора, и он станет полем:
Это одна из немногих возможностей TypeScript, которая генерирует код. Компилятор пишет присваивания за вас:
class Product {
name;
price;
sku;
constructor(name, price, sku) {
this.name = name;
this.price = price;
this.sku = sku;
}
// ...
}
Поскольку это не просто стираемые типы, свойства-параметры не работают при встроенном удалении типов в Node (node file.ts падает с ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX), а параметр компилятора erasableSyntaxOnly сообщает о них как об ошибке TS1294. Проекты, которые запускают файлы .ts напрямую, выписывают поля вручную.
Необязательные параметры и значения по умолчанию
Параметры конструктора подчиняются тем же правилам, что и параметры функций: ? делает параметр необязательным, значение по умолчанию делает его необязательным и подставляет значение, а обязательные параметры идут первыми.
У свойств-параметров тоже могут быть значения по умолчанию. Внутри класса color имеет тип string | undefined, поэтому код, который его использует, должен сначала проверить на undefined.
Для конструкторов с большим числом параметров один объект параметров читается лучше длинного позиционного списка: constructor(opts: { prefix: string; level?: "info" | "debug" }).
Порядок инициализации полей
Инициализаторы полей выполняются до тела конструктора, а свойства-параметры присваиваются в начале тела конструктора. Поэтому инициализатор поля не может прочитать свойство-параметр. TypeScript это ловит:
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.
Во время выполнения инициализатор увидел бы undefined и дал бы NaN. Вычисляйте значение в теле конструктора:
class Grid {
cells: number;
constructor(public size: number) {
this.cells = size * size;
}
}
console.log(new Grid(3).cells); // 9
Перегрузки конструктора
У класса ровно одна реализация конструктора, но над ней можно перечислить несколько сигнатур перегрузки. Вызывающий код видит только перегрузки; сигнатура реализации должна быть совместима со всеми ними.
Перегрузки становится трудно читать после двух-трёх форм. Статические фабричные методы с понятными именами (Color.fromHex("#ff8800"), Color.fromRgb(10, 20, 30)) часто понятнее и не требуют сужения типов внутри одного тела.
Вызов super в подклассе
Производный класс, который объявляет собственный конструктор, должен вызвать super(...) с аргументами родителя, и сделать это до обращения к this. Забытый вызов это ошибка TS2377 (Constructors for derived classes must contain a 'super' call), а использование this до вызова это TS17009 ('super' must be called before accessing 'this' in the constructor of a derived class). Обе ошибки отражают правила JavaScript во время выполнения.
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' ] }
Подкласс, который не добавляет конструктора, наследует конструктор родителя с теми же типами параметров. Подробнее о подклассах в разделе наследование.
Приватные конструкторы и асинхронная настройка
Конструктор private можно вызвать только изнутри класса. Снаружи new это ошибка TS2673. Так вызывающий код направляют через фабрику: для синглтона или для настройки, которая должна быть асинхронной (конструкторы не могут быть async).
private здесь это правило только времени компиляции. В сгенерированном JavaScript обычный конструктор, так что обычный код на JavaScript всё равно может вызвать new Connection(...).
Типы конструкторов
Чтобы принимать класс как значение, типизируйте параметр сигнатурой конструктора: new (...args) => Instance. typeof MyClass тоже работает, но привязывает параметр к сигнатуре конструктора и статическим членам именно этого класса.
ConstructorParameters и InstanceType извлекают кортеж параметров и тип экземпляра из типа конструктора; они описаны вместе с другими служебными типами для функций в разделе ReturnType и Parameters.
Часто задаваемые вопросы
Что такое свойства-параметры в TypeScript?
Параметр конструктора с модификатором (public, private, protected или readonly) объявляет поле и присваивает его за один шаг. constructor(private name: string) {} это сокращение для объявления private name: string и записи this.name = name. Компилятор генерирует это присваивание в выводе.
Может ли у класса TypeScript быть несколько конструкторов?
Несколько реализаций нет. У класса один конструктор, но над ним можно написать несколько сигнатур перегрузки, чтобы вызывающий код видел разные списки параметров. Статические фабричные методы вроде Color.fromHex() и Color.fromRgb() часто понятнее перегрузок.
Может ли конструктор быть асинхронным в TypeScript?
Нет. Конструктор всегда возвращает новый экземпляр, а не промис, и async constructor() отвергается с ошибкой TS1089 ('async' modifier cannot appear on a constructor declaration). Используйте приватный конструктор плюс статический метод async create(), который выполняет асинхронную работу, а затем вызывает new.
Как типизировать конструктор класса как параметр?
Используйте сигнатуру конструктора: new (name: string) => User или typeof User для именно этого класса. Обобщённая фабрика выглядит как function make<T>(ctor: new () => T): T { return new ctor(); }. ConstructorParameters<typeof User> даёт список параметров в виде кортежа.
Почему я получаю "'super' must be called before accessing 'this'"?
Это ошибка TS17009. В классе, который расширяет (extends) другой, объект создаёт родительский конструктор, поэтому this не существует, пока не выполнится super(...). Переместите вызов super выше любой строки, которая обращается к this.