TS классы в TypeScript

Классы в TypeScript расширяют классы ES2015 модификаторами доступа, строгой типизацией свойств, abstract-классами и декораторами, обеспечивая полноценный объектно-ориентированный стиль со статической проверкой.

Зачем нужно

  • Модификаторы private, protected, public выражают инкапсуляцию на уровне компилятора, а не только соглашением
  • TypeScript проверяет совместимость с интерфейсами через implements, что делает полиморфизм безопасным
  • Abstract-классы задают шаблонный метод и гарантируют, что подклассы реализуют обязательные методы

Где используется

  • Сервисы и репозитории в NestJS и Angular
  • Сложные stateful-объекты: WebSocket-менеджер, кэш, FSM (конечный автомат)
  • Базовые классы компонентов в некоторых SPA-фреймворках
  • ООП-реализации паттернов: Strategy, Template Method, Observer

Основной контент

Базовый синтаксис и модификаторы

class Animal {
  public name: string;         // доступно везде
  protected age: number;       // доступно в классе и подклассах
  private _sound: string;      // только внутри класса

  constructor(name: string, age: number, sound: string) {
    this.name = name;
    this.age = age;
    this._sound = sound;
  }

  speak: string {
    return `${this.name} says ${this._sound}`;
  }
}

class Dog extends Animal {
  constructor(name: string) {
    super(name, 0, "woof");
  }

  getAge: number {
    return this.age; // OK — protected
  }
}

const dog = new Dog("Rex");
dog.name;    // OK
dog.age;     // Ошибка — protected

Shorthand в конструкторе

class User {
  // TypeScript создаёт и инициализирует свойства автоматически
  constructor(
    public readonly id: number,
    private name: string,
    protected email: string,
  ) {}

  getInfo: string {
    return `${this.id}: ${this.name}`;
  }
}

Геттеры и сеттеры

class Temperature {
  private _celsius: number;

  constructor(celsius: number) {
    this._celsius = celsius;
  }

  get fahrenheit: number {
    return this._celsius * 9 / 5 + 32;
  }

  set celsius(value: number) {
    if (value < -273.15) throw new RangeError("Below absolute zero");
    this._celsius = value;
  }

  get celsius: number {
    return this._celsius;
  }
}

const t = new Temperature(100);
console.log(t.fahrenheit); // 212
t.celsius = -300; // Ошибка в рантайме

implements — реализация интерфейса

interface Serializable {
  serialize: string;
  deserialize(data: string): this;
}

interface Loggable {
  log: void;
}

class Config implements Serializable, Loggable {
  constructor(private data: Record<string, unknown>) {}

  serialize: string {
    return JSON.stringify(this.data);
  }

  deserialize(raw: string): this {
    this.data = JSON.parse(raw);
    return this;
  }

  log: void {
    console.log(this.data);
  }
}

abstract класс

abstract class Repository<T> {
  abstract findById(id: number): Promise<T | null>;
  abstract save(entity: T): Promise<T>;

  // Шаблонный метод — использует абстрактные
  async findOrFail(id: number): Promise<T> {
    const entity = await this.findById(id);
    if (!entity) throw new Error(`Entity ${id} not found`);
    return entity;
  }
}

class UserRepository extends Repository<User> {
  async findById(id: number): Promise<User | null> {
    // реальный запрос к БД
    return null;
  }

  async save(user: User): Promise<User> {
    // persist
    return user;
  }
}

static члены

class IdGenerator {
  private static counter = 0;

  static next: number {
    return ++IdGenerator.counter;
  }

  static reset: void {
    IdGenerator.counter = 0;
  }
}

IdGenerator.next(); // 1
IdGenerator.next(); // 2

Частые ошибки

  • Не указывать тип свойств класса — при strictPropertyInitialization компилятор требует инициализации или ! (definite assignment)
  • Путать protected и privateprotected доступно в подклассах, private — нет
  • Использовать классы там, где достаточно объекта — в JavaScript/TypeScript функции и объекты часто предпочтительнее классов
  • Не реализовывать все методы abstract класса — компилятор даёт ошибку, но её легко пропустить в сложной иерархии
  • this теряется в callbackthis.method передаётся как callback, this становится undefined; решение — стрелочные методы или .bind(this)

Связанные темы

Ресурсы