Поля и конструктор
Чему вы научитесь:
- объявлять поля класса с типами и находить поле, которое осталось без значения;
- закрывать поля через
private,protectedи#и знать, что из этого работает при запуске; - защищать поля через
readonlyи сокращать конструктор параметрами-свойствами; - переопределять методы через
overrideи задавать общие правила черезabstractиimplements.

Архитектор рисует чертёж дома: сколько в нём комнат, где окна, какого размера кухня. По одному чертежу строят целый квартал, и все дома в нём устроены одинаково. Класс — такой чертёж, а объекты, которые создаёт new, — дома, построенные по нему.
В JavaScript чертёж бывает неполным. Поле появляется в объекте тогда, когда конструктор или метод записал что-то в this. Ошибётесь в имени поля внутри метода, и JavaScript молча заведёт новое поле с опечаткой в имени.
В TypeScript все поля перечисляют в теле класса, и у каждого есть тип. Компилятор работает как инспектор на стройке: сверяет с чертежом каждое обращение к полю. Вот счётчик кликов:
class Counter {
count: number;
step = 1;
constructor(start: number) {
this.count = start;
}
next(): number {
this.count += this.step;
return this.count;
}
}
console.log(new Counter(10).next()); // 11Давайте разберём класс по строкам:
count: number;объявляет полеcountс типомnumber. Значения здесь нет, его даст конструктор.step = 1;объявляет поле сразу со значением. Тип TypeScript выводит из значения:number.- Конструктор принимает
start: numberи кладёт его вthis.count. - Метод
next(): numberприбавляет шаг и возвращает новое значение. Тип результата проверяется так же, как у обычной функции. new Counter(10).next()печатает11.
Поле с типом
Объявление поля похоже на объявление переменной, только без let:
- имя поля —
count; - двоеточие и тип —
: number, такая же аннотация, как у переменной; - начальное значение после
=— необязательная часть, как= 1у поляstep.
Если значение есть, тип можно не писать: TypeScript выведет его сам. Поэтому у step нет аннотации, а поле label = "clicks" получило бы тип string.
Внутри методов tsc знает тип каждого поля. Запись this.count = "много" он не пропустит, как не пропустил бы такое присваивание переменной типа number.
Каждое поле получает значение
Строгий режим проверяет ещё одну вещь: к концу конструктора у каждого поля должно быть значение. Уберём из счётчика конструктор:
class Counter {
count: number; // ✗
step = 1;
}
console.log(new Counter().step);error TS2564: Property 'count' has no initializer and is not definitely assigned in the constructor.Компилятор отвечает: «у свойства count нет начального значения, и конструктор не гарантирует, что присвоит его». В JavaScript такое поле было бы undefined, а тип обещает число. Ошибка ловит это расхождение до запуска.
Исправить можно тремя способами:
- дать полю значение в объявлении:
count = 0;; - присвоить ему значение в конструкторе, как в первом примере;
- разрешить полю быть пустым: у поля
count?: number;типnumber | undefined, и перед использованием его нужно проверять.
Слово «гарантирует» здесь важно. Присваивание внутри if не считается: если условие не выполнится, поле останется пустым. Не считается и присваивание в методе, который вызывает конструктор: tsc смотрит только на сам конструктор.
Класс — это ещё и тип
Имя класса можно писать везде, где пишут тип: у переменной, у параметра, у результата функции.
class Counter {
count = 0;
step = 1;
}
function show(c: Counter): string {
return `${c.count} (+${c.step})`;
}
console.log(show(new Counter())); // 0 (+1)
console.log(show({ count: 5, step: 2 })); // 5 (+2)showпринимает параметр типаCounterи читает его поля.- Первый вызов передаёт объект, созданный через
new Counter(), и печатает0 (+1). - Второй вызов передаёт обычный объект без всякого
new, иtscего тоже пропускает. Программа печатает5 (+2).
Здесь аналогия с чертежом перестаёт работать. Настоящий инспектор спросит, по какому чертежу строили дом. TypeScript сравнивает только форму: подходит любой объект с полями count и step нужных типов. Это та же структурная типизация, что и у интерфейсов.
Частая ошибка
Поле не объявили, а сразу записали в конструкторе, как привыкли в JavaScript:
class Counter {
constructor(start: number) {
this.count = start; // ✗
}
}error TS2339: Property 'count' does not exist on type 'Counter'.В JavaScript запись в this создаёт поле на ходу. TypeScript сверяется с чертежом: в теле класса поля count нет, значит, и записать в него нельзя. Добавьте объявление count: number; над конструктором. Та же проверка ловит опечатки: на this.cuont компилятор скажет, что такого свойства нет, и предложит count.
Главное
- Поля класса объявляют в его теле с типом, например
count: number;, а у поля со значением тип выводится сам. - В строгом режиме каждое поле получает значение в объявлении или в конструкторе, иначе будет ошибка
TS2564. - Записать в
thisполе, которого нет среди объявленных, нельзя: ошибкаTS2339. - Имя класса — это и тип, и к нему подходит любой объект той же формы.