Короткий вступ
TypeScript додає статичну типізацію поверх JavaScript і підтримує класичні концепти ООП: класи, успадкування, інтерфейси, інкапсуляцію, поліморфізм та абстракції. Нижче — практичні приклади та пояснення.
Класи — декларація, конструктор, властивості та методи
class Person {
public name: string;
private age: number;
protected email?: string; // необов’язкове
static species = 'Homo sapiens';
constructor(name: string, age: number, email?: string) {
this.name = name;
this.age = age;
this.email = email;
}
public greet(): string {
return `Hi, I'm ${this.name}.`;
}
private getBirthYear(currentYear: number): number {
return currentYear - this.age;
}
}
const p = new Person('Ivan', 30, 'ivan@example.com');
console.log(p.greet());
// console.log(p.age); // помилка: 'age' приватне
console.log(Person.species);
Успадкування і перевизначення (extends, super)
class Employee extends Person {
constructor(name: string, age: number, public position: string) {
super(name, age);
}
// Перевизначення методу
public greet(): string {
return `${super.greet()} I work as ${this.position}.`;
}
}
const e = new Employee('Olena', 28, 'Backend Developer');
console.log(e.greet());
Access modifiers: public, private, protected, readonly
public — доступно скрізь; private — лише в межах класу; protected — в межах класу і підкласів; readonly — доступне лише для читання після ініціалізації.
class Config {
constructor(public readonly env: string, private secret: string) {}
}
const cfg = new Config('production', 's3cr3t');
// cfg.env = 'dev'; // помилка: readonly
Інтерфейси (interface) — контракти для об'єктів і класів
Інтерфейси описують форму об'єкта або класу. Клас може реалізовувати інтерфейс через implements.
interface Serializable {
serialize(): string;
}
class User implements Serializable {
constructor(public id: number, public name: string) {}
serialize(): string {
return JSON.stringify({ id: this.id, name: this.name });
}
}
Типи (type aliases) — гнучкіші, ніж інтерфейси
type дозволяє створювати псевдоніми типів, об'єднання, перетини тощо.
type ID = string | number;
type ApiResponse = {
data: T;
error?: string;
};
const r: ApiResponse<User> = { data: new User(1, 'Taras') };
Інтерфейс vs Type — коли що використовувати
- Використовуйте
interfaceдля опису публічного API об'єктів та класів (легко розширювати). - Використовуйте
typeдля складних аліасів, уніонів, перетинів або для типів, які не описують об'єкти.
Абстрактні класи (abstract) — часткова реалізація
Абстрактний клас може мати частково реалізовані методи та абстрактні методи, які потрібно реалізувати у підкласах.
abstract class Repository {
abstract getById(id: ID): T | null;
save(entity: T): void {
// загальна логіка збереження
console.log('saving...', entity);
}
}
class UserRepository extends Repository<User> {
getById(id: ID): User | null {
// логіка пошуку
return new User(Number(id), 'Demo');
}
}
Поліморфізм — однаковий інтерфейс, різні реалізації
function printSerializable(obj: Serializable) {
console.log(obj.serialize());
}
const user = new User(2, 'Nastia');
printSerializable(user);
// Можемо передати будь-який об'єкт, що реалізує Serializable
Generics — узагальнені типи
Generics (узагальнення) дозволяють писати код, який працює з різними типами.
class Box<T> {
constructor(public value: T) {}
get(): T { return this.value; }
}
const numBox = new Box<number>(123);
const strBox = new Box<string>('hello');
Enum
enum у TypeScript — це спосіб оголосити набір іменованих констант. Вони допомагають зробити код більш читабельним та захищеним від помилок, замінюючи "магічні рядки" або "числові коди" зрозумілими іменами.
Числові enum (Numeric enums)
enum Direction {
Up, // 0
Down, // 1
Left, // 2
Right // 3
}
const move: Direction = Direction.Left;
console.log(move); // 2
console.log(Direction[2]); // "Left"
Рядкові enum (String enums)
enum Status {
Pending = "PENDING",
InProgress = "IN_PROGRESS",
Done = "DONE"
}
function printStatus(s: Status) {
console.log(`Status: ${s}`);
}
printStatus(Status.InProgress); // Status: IN_PROGRESS
Гетерогенні enum (змішані значення)
enum Mixed {
No = 0,
Yes = "YES"
}
console.log(Mixed.No); // 0
console.log(Mixed.Yes); // "YES"
Const enum (оптимізація компіляції)
const enum замінюється на значення під час компіляції, не створюючи додатковий об’єкт у JS. Це зменшує накладні витрати, але не дозволяє робити reverse mapping.
const enum LogLevel {
Info,
Warn,
Error
}
const level = LogLevel.Warn;
console.log(level); // 1
Порівняння з об’єктами
У нових проєктах замість enum часто рекомендують використовувати as const з об’єктами для кращої сумісності з JavaScript.
const Roles = {
Admin: "ADMIN",
User: "USER",
Guest: "GUEST"
} as const;
type Role = typeof Roles[keyof typeof Roles];
function assignRole(r: Role) {
console.log(`Assigned role: ${r}`);
}
assignRole(Roles.Admin);
enum у TypeScript корисний для опису обмежених наборів значень (стани, ролі, напрямки тощо). Проте в сучасних проєктах часто обирають об’єкти з as const, бо вони легші й простіші у взаємодії з JavaScript.
Інкапсуляція та модулі
За замовчуванням кожний файл — окремий модуль. Експортуйте лише те, що потрібно публічно, а внутрішнє тримайте приватним (не експортуйте).
// user-service.ts
class UserServiceInternal {
// внутрішня логіка
private helper() { /* ... */ }
}
export class UserService {
// публічний API, який використовує внутрішню логіку
private intern = new UserServiceInternal();
find() { /* ... */ }
}
Практичний приклад: інтерфейс сервісу + інжекція залежностей
// repo.ts
export interface IUserRepo {
findById(id: ID): Promise<User | null>;
}
export class UserRepo implements IUserRepo {
async findById(id: ID) { return new User(Number(id), 'From DB'); }
}
// service.ts
import type { IUserRepo } from './repo';
export class UserService {
constructor(private repo: IUserRepo) {}
async getUser(id: ID) {
return this.repo.findById(id);
}
}
// app.ts
import { UserRepo } from './repo';
import { UserService } from './service';
const repo = new UserRepo();
const service = new UserService(repo);
service.getUser(1).then(u => console.log(u));
Поради для Java-розробників
- TypeScript-класи дуже схожі на Java-класи — конструкції знайомі, але синтаксис дещо інший.
- Використовуйте інтерфейси для контрактів і
typeдля складних типових виразів. - При побудові архітектури застосовуйте ті самі принципи SOLID, що й у Java.
- Пам'ятайте, що на рантаймі TypeScript перетворюється в JavaScript — модифікатори доступу (private) забезпечуються транслятором/TypeScript-інструментами, але не є такими ж "залізними", як у JVM.
TypeScript дає всі основні інструменти ООП, знайомі Java-розробникам, при цьому додає гнучкість JavaScript-екосистеми. Правильне використання класів, інтерфейсів, generics та модулів допоможе будувати підтримувану та різко типізовану архітектуру на Node.js.
Коментарі
Дописати коментар