Классы в TypeScript
TypeScript добавляет к классам JavaScript типы и модификаторы видимости.
Эта страница предполагает, что вы уже знаете, как работают обычные классы JavaScript.
Члены класса: типы
Члены класса — его свойства и методы — получают типы с помощью аннотаций типов, точно так же как переменные.
Пример
name: string объявляет свойство, которое должно содержать текст.
Person со свойством name типа string, создайте экземпляр и присвойте его name значение "Jane". Пример ничего не печатает.
Вывод: программа на TypeScript и захваченный вывод
class Person {
name: string;
}
const person = new Person();
person.name = "Jane";
Члены класса: видимость
Члены класса могут также иметь специальные модификаторы, которые определяют, откуда к ним можно обращаться.
Примечание: В TypeScript есть три основных модификатора видимости.
public— (по умолчанию) к члену можно обращаться откуда угодноprivate— к члену можно обращаться только внутри классаprotected— к члену можно обращаться из самого класса и из любого класса, который от него наследуется (см. разделы о наследовании ниже)
Примечание: Внутри класса ключевое слово this обычно указывает на текущий экземпляр класса.
Пример
Вызов getName() допустим, потому что метод публичный. Чтение person.name снаружи класса — ошибка компиляции, потому что name объявлен как private.
private и public у членов класса.
Объявите класс Person с private name: string, public конструктором, который его задаёт, и методом public getName(); создайте Person с именем "Jane", выведите getName() (это разрешено), затем выведите person.name напрямую, чтобы компилятор сообщил, что name объявлен как private и доступен только внутри класса.
Вывод: программа на TypeScript и захваченный вывод
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()); // ошибки нет
console.log(person.name); // Ошибка: свойство 'name' объявлено как private и доступно только внутри класса 'Person'.
Свойства-параметры
TypeScript предлагает сокращённый способ объявления членов класса в конструкторе: добавьте модификатор видимости к параметру конструктора, и он автоматически станет членом класса.
Пример
private name: string в конструкторе одновременно объявляет член name и присваивает ему значение — отдельная строка со свойством не нужна.
Person, конструктор которого принимает private name: string (с пустым телом), плюс метод public getName(), который его возвращает; создайте Person с именем "Jane" и выведите getName(), напечатав Jane.
Вывод: программа на 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 запрещает менять член класса после того, как он получил значение.
Пример
Член с readonly должен получить значение либо там, где он объявлен, либо в конструкторе, и после этого измениться не может.
readonly у свойства класса.
Объявите класс Person с private readonly name: string, задаваемым один раз в конструкторе (с комментарием, поясняющим, что после этого его нельзя изменить), и методом public getName(); создайте Person с именем "Jane" и выведите getName(), напечатав Jane.
Вывод: программа на TypeScript и захваченный вывод
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.
Примечание: Класс может реализовать несколько интерфейсов, перечислив их после implements через запятую, например class Rectangle implements Shape, Colored {
Пример
Rectangle обязан предоставить метод getArea, потому что этого требует интерфейс Shape.
implements.
Объявите интерфейс Shape со свойством-функцией getArea, возвращающим number, затем класс Rectangle, который его реализует, используя свойства-параметры protected readonly width и height и метод getArea(), возвращающий произведение ширины на высоту. Пример только объявляет и ничего не печатает.
Вывод: программа на TypeScript и захваченный вывод
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;
}
}
Наследование: Extends
Класс может строиться на основе другого класса с помощью ключевого слова extends.
Класс может расширять только один другой класс.
Пример
Square расширяет Rectangle, передаёт в super одно и то же значение для ширины и высоты и наследует getArea, не записывая его заново.
extends.
Объявите интерфейс Shape и класс Rectangle, который его реализует (protected readonly width и height, getArea(), возвращающий их произведение), а затем класс Square, который расширяет Rectangle и конструктор которого принимает один width и вызывает super(width, width), наследуя getArea. Пример только объявляет и ничего не печатает.
Вывод: программа на TypeScript и захваченный вывод
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
}
Override
Когда класс расширяет другой класс, он может заменить члены родителя, имеющие то же имя.
Более новые версии TypeScript позволяют отметить это явно ключевым словом override.
Примечание: По умолчанию ключевое слово override при переопределении метода необязательно; оно лишь помогает ловить ошибки, например «переопределение» метода, которого у родителя нет. Включите настройку noImplicitOverride, чтобы сделать его обязательным.
Пример
Square заменяет метод toString, унаследованный от Rectangle. Члены с protected по-прежнему доступны из Square.
override.
Объявите интерфейс Shape, класс Rectangle, который его реализует, с protected readonly width и height, методом getArea() и методом toString(), возвращающим "Rectangle[width=..., height=...]", а затем подкласс Square, чей override toString() возвращает "Square[width=...]". Пример только объявляет и ничего не печатает.
Вывод: программа на TypeScript и захваченный вывод
interface Shape {
getArea: () => number;
}
class Rectangle implements Shape {
// использование protected для этих членов открывает доступ к ним из классов, которые расширяют этот класс, например из Square
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}]`;
}
}
Абстрактные классы
Класс можно написать так, чтобы он служил только основой для других классов, не реализуя все свои члены.
Это делается с помощью ключевого слова abstract у класса.
Члены, оставленные без реализации, тоже используют ключевое слово abstract.
Примечание: Абстрактный класс нельзя создать напрямую, потому что реализованы не все его члены.
Пример
Polygon оставляет getArea абстрактным, но реализует toString с его помощью. Rectangle расширяет Polygon и предоставляет недостающий getArea.
abstract class Polygon с public abstract getArea(): number и конкретным toString(), возвращающим "Polygon[area=...]", а затем класс Rectangle, который его расширяет, с protected readonly width и height, вызывает super() и реализует getArea(). Пример только объявляет и ничего не печатает.
Вывод: программа на TypeScript и захваченный вывод
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;
}
}