Классы
Классы в TypeScript расширяют ES6 классы типизацией свойств, implements для интерфейсов, модификаторами доступа и static-членами.
Зачем нужно
- TypeScript добавляет к классам типизацию: свойства с типами, implements, модификаторы доступа
- Классы — основной механизм ООП в TypeScript
- Используются в Angular, NestJS, TypeORM и других фреймворках
- Позволяют инкапсулировать данные и поведение с проверкой типов
Где используется
- Backend: NestJS контроллеры, сервисы, модули
- ORM: TypeORM, Prisma entities
- Frontend: Angular компоненты и сервисы
- Паттерны: Repository, Service, Factory, Singleton
Предпосылки
- Интерфейсы — контракты для классов
- ES6 классы в JavaScript
Базовый синтаксис
class User {
// Объявление свойств с типами (обязательно в TS!)
id: number;
name: string;
email: string;
constructor(id: number, name: string, email: string) {
this.id = id;
this.name = name;
this.email = email;
}
greet: string {
return `Hello, I'm ${this.name}`;
}
}
const user = new User(1, "Alice", "alice@example.com");
console.log(user.greet); // "Hello, I'm Alice"
Parameter Properties (сокращённый синтаксис)
TypeScript позволяет объявлять и инициализировать свойства прямо в параметрах конструктора:
class User {
// Модификатор в параметре = объявление + инициализация
constructor(
public id: number,
public name: string,
public email: string,
private password: string
) {}
// Эквивалентно ручному: this.id = id; this.name = name; ...
greet: string {
return `Hello, I'm ${this.name}`;
}
}
const user = new User(1, "Alice", "a@b.com", "secret");
user.name; // OK — public
user.password; // Ошибка! — private
Implements — реализация интерфейсов
interface Serializable {
serialize: string;
}
interface Validatable {
validate: boolean;
}
class User implements Serializable, Validatable {
constructor(
public name: string,
public email: string
) {}
serialize: string {
return JSON.stringify({ name: this.name, email: this.email });
}
validate: boolean {
return this.name.length > 0 && this.email.includes("@");
}
}
// Implements проверяет контракт, но НЕ добавляет типы автоматически
// Класс должен реализовать все методы/свойства интерфейса
Наследование (extends)
class Animal {
constructor(public name: string) {}
move(distance: number): void {
console.log(`${this.name} moved ${distance}m`);
}
}
class Dog extends Animal {
constructor(name: string, public breed: string) {
super(name); // Обязательный вызов super
}
bark: void {
console.log("Woof!");
}
// Переопределение метода
override move(distance: number): void {
console.log("Running...");
super.move(distance); // Вызов родительского метода
}
}
const dog = new Dog("Rex", "Shepherd");
dog.bark; // "Woof!"
dog.move(10); // "Running..." + "Rex moved 10m"
noImplicitOverride
// tsconfig.json: "noImplicitOverride": true
class Child extends Animal {
// Без override — ошибка!
move(distance: number): void {} // Error: must use 'override'
// Правильно:
override move(distance: number): void {
super.move(distance);
}
}
Static Members
class MathUtils {
static PI = 3.14159;
static sum(a: number, b: number): number {
return a + b;
}
static random(min: number, max: number): number {
return Math.floor(Math.random * (max - min + 1)) + min;
}
}
MathUtils.PI; // 3.14159
MathUtils.sum(1, 2); // 3
// Static блоки (ES2022+)
class Config {
static instance: Config;
static {
Config.instance = new Config();
}
}
Singleton через static
class Database {
private static instance: Database;
private constructor(private connectionString: string) {}
static getInstance: Database {
if (!Database.instance) {
Database.instance = new Database("postgres://localhost:5432/db");
}
return Database.instance;
}
query(sql: string): unknown {
console.log(`Executing: ${sql}`);
return ;
}
}
const db = Database.getInstance;
db.query("SELECT * FROM users");
// new Database("..."); // Ошибка! Constructor is private
Getters и Setters
class Temperature {
private _celsius: number;
constructor(celsius: number) {
this._celsius = celsius;
}
// Getter
get fahrenheit: number {
return this._celsius * 9 / 5 + 32;
}
// Setter с валидацией
set fahrenheit(value: number) {
this._celsius = (value - 32) * 5 / 9;
}
get celsius: number {
return this._celsius;
}
set celsius(value: number) {
if (value < -273.15) {
throw new Error("Temperature below absolute zero");
}
this._celsius = value;
}
}
const temp = new Temperature(100);
console.log(temp.fahrenheit); // 212
temp.fahrenheit = 32;
console.log(temp.celsius); // 0
Класс как тип
class User {
constructor(public name: string, public age: number) {}
}
// User — и конструктор, и тип одновременно
const user: User = new User("Alice", 30);
// Структурная типизация — объект тоже подходит!
const fakeUser: User = { name: "Bob", age: 25 }; // OK, если структура совпадает
// typeof класса — тип конструктора
function createInstance(Cls: typeof User, name: string, age: number): User {
return new Cls(name, age);
}
// Или через конструкторную сигнатуру
function create(Cls: new (name: string, age: number) => User): User {
return new Cls("test", 0);
}
Generic Classes
class Result<T, E = Error> {
private constructor(
private readonly value: T | null,
private readonly error: E | null
) {}
static ok<T>(value: T): Result<T, never> {
return new Result(value, null);
}
static err<E>(error: E): Result<never, E> {
return new Result(null, error);
}
isOk: boolean {
return this.error === null;
}
unwrap: T {
if (this.value === null) throw new Error("Called unwrap on error");
return this.value;
}
unwrapOr(defaultValue: T): T {
return this.value ?? defaultValue;
}
}
const success = Result.ok(42);
const failure = Result.err(new Error("Not found"));
console.log(success.unwrap); // 42
console.log(failure.unwrapOr(0)); // 0
Частые ошибки
- Забыть объявить свойство — в TS свойства нужно объявлять до использования
class Bad {
constructor(name: string) {
this.name = name; // Ошибка! Property 'name' does not exist
}
}
// Правильно: parameter property или объявление
class Good {
constructor(public name: string) {} // Parameter property
}
- strictPropertyInitialization — все свойства должны быть инициализированы
class User {
name: string; // Ошибка! Not initialized
// Решения:
name: string = ""; // Значение по умолчанию
name!: string; // Definite assignment assertion (осторожно!)
name: string | undefined; // Явно допустить undefined
}
- this в callback — потеря контекста
class Counter {
count = 0;
// Проблема: this потеряется в callback
increment {
this.count++;
}
// Решение: arrow function property
increment = () => {
this.count++;
};
}
- implements не добавляет типы — класс сам должен всё объявить
- Структурная типизация — объект с нужной структурой совместим с классом
Практика
- Создайте класс
Stack<T>с методами push, pop, peek, size - Реализуйте Singleton-паттерн для класса
Logger - Создайте класс
Userс implementsSerializableиValidatable - Напишите generic класс
Result<T, E>с методами ok, err, unwrap - Используйте getter/setter для валидации данных
Связанные темы
- Абстрактные классы — abstract классы
- Модификаторы доступа — public, private, protected
- Декораторы — метаданные для классов
- Интерфейсы — контракты для implements