Продолжая серию статей о TypeScript, поговорим о классах. Синтаксис классов TS во многом повторяет возможности спецификации ES6, поэтому в этой статье будут рассмотрены дополнения, которые расширяют JavaScript классы.
Свойства класса
В JavaScript нет возможности назначить свойства при объявлении класса — все необходимые значения нужно определять в конструкторе или других методах. При таком подходе объявление свойств неявное, не всегда ясно какие свойства имеет класс. TS решает эту проблему: здесь можно не только объявить свойства класса, но и назначить им начальные значения:
class Phone {
    brand: string;
    type: string = 'Smartphone';
    constructor (brand: string, type?: string) {
        this.brand = brand;
        if (type) this.type = type;
    }
}
Модификаторы доступа
Одним из недостатков ES6 классов является невозможность сделать методы и свойства приватными. В TS есть привычные модификаторы: public, private и protected, которые можно использовать как для методов, так и для свойств. По умолчанию, как и в других языках, все свойства имеют модификатор public:
class Phone {
    brand: string;
    type: string = 'Smartphone';
    private serialNumber: number;
    constructor (brand: string, serialNumber: number, type?: string) {
        this.brand = brand;
        this.serialNumber = serialNumber;
        if (type) this.type = type;
    }
}
Важно отметить, что наличие private или protected не означает приватность этих методов во внешнем коде. При попытке доступа к приватному свойству из TS кода компилятор выдаст ошибку, но после компиляции эти свойства все равно будут доступны из внешнего кода.
Readonly свойства
В статье об интерфейсах мы рассматривали readonly свойства, они применимы и к классам. Они могут быть инициализированы только при объявлении или в конструкторе. В других местах присваивание значения readonly свойству вызовет ошибку компиляции.
class Phone {
    readonly brand: string;
    readonly type: string = 'Smartphone';
    constructor (brand: string, type?: string) {
        this.brand = brand;
        if (type) this.type = type;
    }
}
Помимо объявления таких свойств стандартным образом, TS предоставляет возможность определять их в параметрах конструктора:
class Phone {
    readonly type: string = 'Smartphone';
    constructor (readonly brand: string, type?: string) {
        if (type) this.type = type;
    }
}
const phone = new Phone('iPhone');
Переданные значения будут назначены в соответствующие свойства. Такая запись сокращает код, но делает объявление свойств менее явным.
Абстрактные классы
С помощью TypeScript можно описывать абстрактные классы. Они могут содержать реализацию общих для дочерних классов методов, а также абстрактные методы, которые должны быть реализованы в классах-потомках:
abstract class Phone {
    readonly type: string = 'Smartphone';
    constructor (readonly brand: string, type?: string) {
        if (type) this.type = type;
    }
    call (name: string) {
      console.log(`Calling ${name}`);
    }
    abstract charge (charger: string);
}
class iPhone extends Phone {
    constructor () {
        super('Apple');
    }
    charge (charger: string) {
        if (charger !== 'lightning') {
            throw Error('iPhone doesn\'t support this charger');
        }
    }
}
class Pixel extends Phone {
    constructor () {
        super('Google');
    }  
    charge (charger: string) {
        if (charger !== 'USB-C') {
            throw Error('Pixel doesn\'t support this charger');
        }
    }
}
const pixel = new Pixel();
pixel.charge('USB-C');
pixel.call('Mom')
Классы как интерфейсы
В предыдущей статье был рассмотрен случай наследования одного интерфейса от другого. Но TS позволяет также наследовать интерфейс от класса. Дело в том, что при объявлении класса создаются две сущности: тип, который описывает поля и методы, и функция-конструктор, которая создает объект класса. Поскольку в TypeScript применяется «утиная» типизация, интерфейсы по сути и являются типами данных. А значит можно расширить созданный при объявлении класса тип новым интерфейсом:
class Phone {
    readonly brand: brand;
}
interface AndroidPhone extends Phone {
    androidVersion: string;
}
Не смотря на множество вариантов описания пользовательского типа данных, все они выполняют одну и ту же функцию. Использование того или иного способа остается на усмотрение разработчика.
Коротко о главном
Дополнительные возможности TypeScript для описания классов позволяют сильно улучшить читабельность и понимание кода, а также помогают структурировать иерархию и отношения между классами. Строгая типизация и наличие модификаторов доступа позволяет устранять потенциальные ошибки еще при написании кода. Но стоить помнить о том, что все модификаторы существуют только на уровне TS. После компиляции приватные свойства будут доступны из внешнего кода, а readonly свойству можно будет присвоить значение.