Классы в TypeScript это классы JavaScript плюс проверки при компиляции. Важно понимать, какие проверки исчезают в рантайме, а какие нет.
Поля и модификаторы доступа
class User {
public name: string; // public по умолчанию
protected role = 'user'; // в классе и наследниках
private token?: string; // только внутри класса, проверка компилятора
#secret = 42; // настоящее приватное поле JS
readonly id: number;
constructor(id: number, name: string) {
this.id = id;
this.name = name;
}
}
private существует только в типах: в рантайме поле обычное, и (user as any).token его прочитает. #secret приватно на уровне языка: снаружи до него не добраться даже через any.
readonly запрещает присваивание вне конструктора, но тоже только при компиляции.
Параметры-свойства
class Point {
constructor(public x: number, private readonly y: number) {}
}
Модификатор в параметре конструктора объявляет и заполняет поле. Это синтаксис TypeScript, он не работает в режиме erasableSyntaxOnly (TS 5.8) и при запуске .ts через Node.js с отсечением типов, потому что требует генерации кода.
implements и extends
interface Serializable { toJSON(): string }
class Order implements Serializable {
toJSON() { return '{}'; }
}
implements только проверяет, что класс соответствует интерфейсу. Типы параметров методов из интерфейса не переносятся в класс, их нужно писать.
extends наследует реализацию. Класс наследует один класс, но реализует сколько угодно интерфейсов. Интерфейс может расширять несколько интерфейсов и даже класс.
Абстрактные классы
abstract class Shape {
abstract area(): number;
describe() { return `Площадь ${this.area()}`; }
}
class Circle extends Shape {
constructor(private r: number) { super(); }
area() { return Math.PI * this.r ** 2; }
}
new Shape(); // ошибка
В отличие от интерфейса, абстрактный класс существует в рантайме и может содержать готовую реализацию и состояние.
override
С флагом noImplicitOverride метод, переопределяющий родительский, обязан иметь override. Если метод в родителе переименуют, компилятор сразу покажет, что переопределять нечего.
Структурная совместимость классов
Классы сравниваются по форме, как интерфейсы:
class A { x = 1 }
class B { x = 2 }
const a: A = new B(); // ок
Исключение: если в классе есть private или protected поле, совместимы только экземпляры этого класса и его наследников.
Типы для самого класса
typeof User это тип конструктора, User в позиции типа это тип экземпляра. Для фабрик пишут new (...args: any[]) => T.
Частые ошибки
- Думать, что
privateзащищает данные в рантайме. - Ждать вывода типов параметров из
implements. - Путать
abstract classиinterface: первый попадает в бандл. - Передавать метод как колбэк и терять
this. Помогают стрелочные поля илиbind.
Что спросят на собесе
- Разница
privateи#private. - Абстрактный класс против интерфейса: когда что.
- Что проверяет
implements. - Что такое параметры-свойства конструктора.
- Почему два разных класса с одинаковыми полями совместимы.