Продолжая серию статей о 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 свойству можно будет присвоить значение.