Menu

Конструктор в TypeScript: свойства-параметры и перегрузки

Как типизировать конструктор класса в TypeScript: типизированные и необязательные параметры, свойства-параметры вроде constructor(private name: string), порядок инициализации полей, перегрузки конструктора, вызов super в подклассах, приватные конструкторы и типы конструкторов с new.

На этой странице есть исполняемые редакторы: меняйте, запускайте и сразу видите результат.

Конструктор в 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.

Coddy programming languages illustration

Учитесь программировать с Coddy

НАЧАТЬ