TypeScript Классы

TypeScript дополняет классы JavaScript типами и модификаторами видимости.

Подробнее о классах JavaScript читайте здесь.


Члены класса: типы

Члены класса (свойства и методы) типизируются с помощью аннотаций типов, аналогичных типизации переменных.

Пример


class Person {
  name: string;
}

const person = new Person();
person.name = "Jane";


Члены класса: видимость

Членам класса можно задать специальные модификаторы, управляющие их видимостью.

В TypeScript есть три основных модификатора видимости:

  • public — (по умолчанию) разрешает доступ к члену класса откуда угодно.
  • private — разрешает доступ к члену класса только изнутри самого класса.
  • protected — разрешает доступ к члену класса самому классу и любым классам-наследникам (детально рассмотрено в разделе про наследование ниже).

Пример


class Person {
  private name: string;

  public constructor(name: string) {
    this.name = name;
  }

  public getName(): string {
    return this.name;
  }
}

const person = new Person("Jane");
console.log(person.getName()); // person.name недоступен извне класса, так как он приватный

Ключевое слово this в классе обычно ссылается на экземпляр класса. Подробнее о this читайте здесь.


Свойства-параметры

TypeScript предоставляет удобный способ определения членов класса прямо в конструкторе, добавив модификатор видимости к параметрам.

Свойство-параметр объединяет объявление свойства класса и передачу его значения в конструкторе в единую инструкцию.

Пример


class Person {
  // name — приватное свойство класса
  public constructor(private name: string) {}

  public getName(): string {
    return this.name;
  }
}

const person = new Person("Jane");
console.log(person.getName());

Ограничения:

  • Только в конструкторе: свойства-параметры можно использовать только в конструкторе класса.
  • Типизация: тип свойства должен быть явно указан при объявлении.

Константные члены (readonly)

Похожим образом, как и с массивами, ключевое слово readonly может запретить изменение члена класса.

Пример


class Person {
  private readonly name: string;

  public constructor(name: string) {
    // name не может быть изменён после первой инициализации
    this.name = name;
  }

  public getName(): string {
    return this.name;
  }
}

const person = new Person("Jane");
console.log(person.getName());


Наследование: реализация интерфейса (implements)

Интерфейсы могут быть использованы для определения типа, которому должен следовать класс, с помощью ключевого слова implements.

Пример


interface Shape {
  getArea: () => number;
}

class Rectangle implements Shape {
  public constructor(protected readonly width: number, protected readonly height: number) {}

  public getArea(): number {
    return this.width * this.height;
  }
}

Класс может реализовать несколько интерфейсов, перечислив их через запятую после слова implements:


class Rectangle implements Shape, Colored {
  // ...
}


Наследование: расширение класса (extends)

Классы могут расширять друг друга с помощью ключевого слова extends.

Один класс может расширить только один другой класс.

Пример


interface Shape {
  getArea: () => number;
}

class Rectangle implements Shape {
  public constructor(protected readonly width: number, protected readonly height: number) {}

  public getArea(): number {
    return this.width * this.height;
  }
}

class Square extends Rectangle {
  public constructor(width: number) {
    super(width, width);
  }

  // getArea наследуется от Rectangle
}


Переопределение

Когда класс расширяет другой класс, он может перезаписать члены родительского класса с тем же именем.

Новые версии TypeScript позволяют явно отмечать это с помощью ключевого слова override.

Пример


interface Shape {
  getArea: () => number;
}

class Rectangle implements Shape {
  public constructor(protected readonly width: number, protected readonly height: number) {}

  public getArea(): number {
    return this.width * this.height;
  }

  public toString(): string {
    return `Rectangle[width=${this.width}, height=${this.height}]`;
  }
}

class Square extends Rectangle {
  public constructor(width: number) {
    super(width, width);
  }

  // Этот toString заменяет toString из Rectangle
  public override toString(): string {
    return `Square[width=${this.width}]`;
  }
}

По умолчанию ключевое слово override не обязательно при переопределении метода, оно лишь помогает избежать случайного переопределения несуществующего метода.

Используйте опцию noImplicitOverride, чтобы принудительно требовать использование этого ключевого слова при переопределении.


Абстрактные классы

Классы могут быть созданы таким образом, чтобы служить основой для других классов, не требуя реализации всех членов.

Это достигается с помощью ключевого слова abstract.

Не реализованные члены также маркируются ключевым словом abstract.

Пример


abstract class Polygon {
  public abstract getArea(): number;

  public toString(): string {
    return `Polygon[area=${this.getArea()}]`;
  }
}

class Rectangle extends Polygon {
  public constructor(protected readonly width: number, protected readonly height: number) {
    super();
  }

  public getArea(): number {
    return this.width * this.height;
  }
}

Нельзя напрямую создать экземпляр абстрактного класса, так как они не реализуют все свои члены.