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иprivate—protectedдоступно в подклассах,private— нет - Использовать классы там, где достаточно объекта — в JavaScript/TypeScript функции и объекты часто предпочтительнее классов
- Не реализовывать все методы
abstractкласса — компилятор даёт ошибку, но её легко пропустить в сложной иерархии thisтеряется в callback —this.methodпередаётся как callback,thisстановитсяundefined; решение — стрелочные методы или.bind(this)