Skip to content

Найпопулярніші запитання та відповіді на співбесіді з TypeScript

License

Notifications You must be signed in to change notification settings

DevLoversTeam/typescript-interview-questions

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

TypeScript

Найпопулярніші запитання та відповіді на співбесіді з TypeScript

1. Поясніть, що таке TypeScript та які його ключові відмінності від JavaScript.

TypeScript

TypeScript — це надбудова над JavaScript, яка додає статичну типізацію, інтерфейси та інші можливості для підвищення надійності коду.

Відмінності:

  • Типізація: TS має статичні типи, JS — динамічні.

  • Розробка: TS виявляє помилки на етапі компіляції, JS — під час виконання.

  • Сумісність: TS компілюється у JS, тому працює у всіх середовищах JS.

  • Інструменти: краща підтримка IDE (автодоповнення, рефакторинг).

2. Що означає твердження, що TypeScript є надмножиною JavaScript?

TypeScript

  • Це означає, що будь-який коректний JavaScript-код є також коректним TypeScript-кодом. TypeScript розширює можливості JS, додаючи типи та інші фічі, але при цьому не змінює базову мову.
3. Які основні вбудовані типи даних підтримує TypeScript?

TypeScript

Основні типи в TypeScript:

  • string — рядки

  • number — числа (цілі та з плаваючою крапкою)

  • boolean — логічні значення

  • null та undefined

  • any — будь-який тип

  • unknown — невідомий тип (безпечніша альтернатива any)

  • void — відсутність значення (часто у функціях)

  • never — функція ніколи не повертає значення (наприклад, кидає помилку)

  • object — об’єкти

  • Масиви (type[] або Array)

  • Кортежі ([type1, type2, ...])

  • enum — перерахування

4. Які способи оголошення змінних у TypeScript та як до них застосовується типізація?

TypeScript

У TypeScript змінні оголошуються так само, як у JavaScript: let, const, рідше var.

Тип можна:

  • вивести автоматично (Type Inference):
let age = 25; // type: number
  • задати явно:
let age: number = 25;
const name: string = "Alice";

Зазвичай рекомендують використовувати const для незмінних значень, let для змінних, а явну типізацію — там, де виведення типу неочевидне.

5. Що таке інтерфейси в TypeScript і для чого вони використовуються?

TypeScript

Інтерфейси в TypeScript описують структуру об’єкта (його властивості та їх типи), не створюючи конкретної реалізації. Вони допомагають забезпечити контракт між частинами коду.

Основні можливості:

  • Опис форми об’єкта:
interface User {
  id: number;
  name: string;
  isAdmin?: boolean; // необов’язкове поле
}

const user: User = { id: 1, name: "Alice" };
  • Підтримка опціональних властивостей (?).

  • Можливість розширення (extends).

  • Використання для опису структур функцій, класів та масивів.

По суті, інтерфейси — це спосіб зробити код більш передбачуваним і безпечним.

6. Що таке enum у TypeScript та в яких випадках його доцільно використовувати?

TypeScript

  • enum (перерахування) — це тип, який дозволяє задати набір іменованих констант.

Види:

  • Numeric enum (значення автоматично інкрементуються):
enum Direction {
  Up,    // 0
  Down,  // 1
  Left,  // 2
  Right  // 3
}
  • String enum:
enum Role {
  Admin = "ADMIN",
  User = "USER",
  Guest = "GUEST"
}

Використовується, коли є обмежений набір варіантів (напр. ролі користувачів, статуси замовлення, напрямки руху). Це робить код більш читабельним і безпечним, ніж "магічні числа" чи рядки.

7. Як правильно оголошувати та використовувати функції в TypeScript із врахуванням типів?

TypeScript

  • Функції визначаються так само, як у JavaScript, але в TypeScript можна явно задавати типи параметрів і результату:
// З явними типами
function add(a: number, b: number): number {
  return a + b;
}

// Функціональний вираз
const greet = (name: string): string => {
  return `Hello, ${name}`;
};

// Необов’язковий параметр
function log(message: string, userId?: number): void {
  console.log(message, userId);
}
  • Параметри можна робити обов’язковими, необов’язковими (?) або мати значення за замовчуванням.

  • Тип повернення можна вивести автоматично, але для складних функцій краще вказувати явно.

  • Для callback-ів та складних сигнатур використовують типи або інтерфейси функцій.

8. Що таке виведення типу (type inference) у TypeScript і як воно працює?

TypeScript

  • Виведення типу — це механізм, коли TypeScript автоматично визначає тип змінної чи результату функції на основі наданого значення без явного оголошення.

Приклади:

let count = 10;    // TS виводить: number
let message = "Hi"; // TS виводить: string

function add(a: number, b: number) {
  return a + b; // TS виводить: number (тип повернення)
}
  • Перевага: менше коду, але збережена типобезпека.

  • Ризик: у складних випадках краще явно вказувати тип, щоб уникнути неочікуваного any.

9. У чому різниця між let і const у TypeScript і як правильно їх використовувати?

TypeScript

let — дозволяє оголосити змінну, значення якої можна змінювати. Має блочну область видимості.

const — створює змінну, якій можна призначити значення лише один раз. Також має блочну область видимості.

Приклад:

let counter: number = 1;
counter = 2; // ✅ можна

const name: string = "Alice";
name = "Bob"; // ❌ помилка

Рекомендація: за замовчуванням використовувати const, а let — лише коли змінна дійсно змінюється.

Важливо: const не робить об’єкт immutable, змінювати внутрішні властивості все одно можна:

const user = { id: 1, name: "Alice" };
user.name = "Bob"; // ✅ дозволено
10. Яким чином можна скомпілювати TypeScript-файли у JavaScript?

TypeScript

  • Використовується TypeScript Compiler (tsc).

  • Основні варіанти:

# компіляція одного файлу

tsc file.ts

# компіляція проєкту з налаштуваннями tsconfig.json

tsc
  • У tsconfig.json можна задати цільову версію JS (target), директорію виводу (outDir), модулі (module) тощо.

  • Також можна включити watch mode:

tsc -w

У реальних проєктах часто використовують Babel, Webpack, Vite чи ts-node для інтеграції компіляції у збірку чи запуск коду напряму.

11. Що таке класи в TypeScript і чим вони відрізняються від класів у звичайному JavaScript?

TypeScript

Класи в TypeScript — це надбудова над JS-класами. Вони працюють так само, як у JS, але доповнені системою типів:

  • можна оголошувати типи для полів, параметрів і повертаних значень;

  • є модифікатори доступу (public, private, protected, readonly);

  • є abstract класи та методи;

  • підтримка implements для інтерфейсів;

  • підтримка generics.

У рантаймі вони компілюються в звичайні JS-класи, а типи прибираються.

12. Як правильно реалізувати спадкування класів у TypeScript?

TypeScript

Використовується ключове слово extends. Базовий клас може мати загальні властивості/методи, похідний — успадковує їх і може перевизначати. При перевизначенні конструктора обов’язково викликається super().

class Animal {
  constructor(public name: string) {}
  speak(): void {
    console.log(`${this.name} makes a sound.`);
  }
}

class Dog extends Animal {
  constructor(name: string, public breed: string) {
    super(name);
  }
  speak(): void {
    console.log(`${this.name} barks.`);
  }
}

const rex = new Dog("Rex", "Labrador");
rex.speak(); // Rex barks.
13. Які є модифікатори доступу в TypeScript і як вони впливають на властивості та методи класів?

TypeScript

TypeScript має 4 модифікатори доступу:

  • public (за замовчуванням) – доступний скрізь.

  • private – доступний тільки всередині цього класу.

  • protected – доступний у класі та його нащадках.

  • readonly – властивість доступна тільки для читання після ініціалізації.

Вони впливають лише на етапі компіляції (для контролю типів), у рантаймі JavaScript цього обмеження немає.

14. Що таке абстрактні класи в TypeScript і для чого їх використовують?

TypeScript

Абстрактний клас — це клас, який не можна інстанціювати напряму. Він може містити:

  • реалізовані методи, які спільні для всіх нащадків,

  • abstract методи без реалізації, які зобов’язані реалізувати похідні класи.

Призначення: задавати загальний контракт і базову поведінку для групи класів, залишаючи конкретну реалізацію нащадкам.

abstract class Shape {
  constructor(public color: string) {}
  abstract area(): number; // має реалізувати підклас
  describe(): void {
    console.log(`This shape is ${this.color}`);
  }
}

class Circle extends Shape {
  constructor(color: string, public radius: number) {
    super(color);
  }
  area(): number {
    return Math.PI * this.radius ** 2;
  }
}

const c = new Circle("red", 5);
c.describe(); // This shape is red
console.log(c.area()); // 78.5398...
15. Як працюють конструктори в класах TypeScript і які особливості їх використання?

TypeScript

Конструктор (constructor) — це метод для ініціалізації об’єкта класу. Особливості у TypeScript:

  • можна задавати типи параметрів;

  • можна використовувати модифікатори доступу прямо в параметрах (public, private, protected, readonly) — тоді TypeScript автоматично створює відповідні поля;

  • у похідних класах обов’язково викликається super() перед використанням this.

Приклад:

class Person {
  constructor(public name: string, private age: number) {}
  greet() {
    console.log(`Hi, my name is ${this.name}`);
  }
}

class Employee extends Person {
  constructor(name: string, age: number, public position: string) {
    super(name, age);
  }
}

const emp = new Employee("Alice", 30, "Developer");
emp.greet(); // Hi, my name is Alice
console.log(emp.position); // Developer
16. Що таке декоратори в TypeScript для властивостей класу і як їх застосовувати?

TypeScript

Декоратори — це функції, які дозволяють змінювати або розширювати поведінку класів, методів, властивостей або параметрів. Декоратор властивості отримує ціль (target) та ім’я властивості (property key).

Приклад використання властивості:

function logProperty(target: any, key: string) {
  let value = target[key];

  const getter = () => {
    console.log(`Getting ${key}: ${value}`);
    return value;
  };

  const setter = (newVal: any) => {
    console.log(`Setting ${key} to ${newVal}`);
    value = newVal;
  };

  Object.defineProperty(target, key, {
    get: getter,
    set: setter,
    enumerable: true,
    configurable: true
  });
}

class Person {
  @logProperty
  name: string = "";
}

const p = new Person();
p.name = "Alice"; // Setting name to Alice
console.log(p.name); // Getting name: Alice

Декоратори часто використовують для логування, валідації, DI (dependency injection) та метаданих.

17. Як реалізуються та працюють геттери (get) і сеттери (set) у TypeScript?

TypeScript

Геттери та сеттери дозволяють контролювати доступ до властивостей класу.

  • get — повертає значення властивості, дозволяє виконувати додаткову логіку при читанні.

  • set — задає значення властивості, дозволяє перевіряти або модифікувати його перед присвоєнням.

Приклад:

class Person {
  private _age: number = 0;

  get age(): number {
    return this._age;
  }

  set age(value: number) {
    if (value < 0) throw new Error("Age cannot be negative");
    this._age = value;
  }
}

const p = new Person();
p.age = 25;          // викликається set
console.log(p.age);  // викликається get -> 25

Геттери і сеттери працюють як звичайні властивості при доступі, але дозволяють інкапсулювати логіку.

18. Як реалізується перевантаження методів у TypeScript?

TypeScript

TypeScript дозволяє перевантажувати методи через сигнатури, але тільки одна реалізація. Це означає: можна оголосити кілька варіантів виклику методу з різними параметрами, а в тілі методу реалізувати логіку з перевіркою типів/кількості аргументів.

Приклад:

class Calculator {
  add(a: number, b: number): number;
  add(a: string, b: string): string;
  add(a: any, b: any): any { // реальна реалізація
    return a + b;
  }
}

const calc = new Calculator();
console.log(calc.add(2, 3));       // 5
console.log(calc.add("Hello, ", "TS")); // Hello, TS

Особливості:

  • Сигнатури визначають дозволені варіанти виклику.

  • Реалізація повинна враховувати всі варіанти.

  • У рантаймі перевантаження як у C#/Java не існує, це чисто типізаційний механізм.

19. Для чого використовується ключове слово static у класах TypeScript?

TypeScript

static дозволяє створювати члени класу (властивості або методи), які належать самому класу, а не його екземплярам.

  • До них звертаються через ім’я класу (ClassName.member), а не через об’єкт.

  • Можна використовувати для констант, утилітарних методів та лічильників.

Приклад:

class Counter {
  static count = 0;

  static increment() {
    Counter.count++;
  }
}

Counter.increment();
console.log(Counter.count); // 1

const c = new Counter();
// c.increment(); // ❌ помилка, increment — static
20. Як створити власний тип у TypeScript за допомогою псевдоніму (type alias)?

TypeScript

Псевдонім типу (type) дозволяє створити нове ім’я для будь-якого типу, включно з об’єднаннями (union), перетинами (intersection) та функціями. Це зручно для складних типів, повторного використання і документації коду.

type ID = string | number;
type User = {
  id: ID;
  name: string;
  age?: number; // необов’язкове поле
};

type Callback = (result: string) => void;

Використовуємо як звичайний тип:

const user: User = { id: 1, name: "Alice" };

Псевдоніми не створюють нових типів у рантаймі — це чисто типізація на етапі компіляції.

21. Що таке union-типи в TypeScript і як їх застосовувати?

TypeScript

Union-тип (|) дозволяє змінній або параметру приймати декілька можливих типів. Це зручно, коли значення може бути різного виду.

Приклад:

type ID = string | number;

function printId(id: ID) {
  if (typeof id === "string") {
    console.log("ID (string): " + id.toUpperCase());
  } else {
    console.log("ID (number): " + (id * 10));
  }
}

printId("abc"); // ID (string): ABC
printId(123); // ID (number): 1230

Особливості:

  • Потрібно робити type narrowing (перевірку типу) перед використанням специфічних методів.

  • Можна комбінувати кілька типів, навіть null | undefined.

22. Що таке intersection-типи в TypeScript і як вони працюють?

TypeScript

Intersection-тип (&) поєднує кілька типів в один. Об’єкт повинен відповідати всім об’єднаним типам одночасно. Це зручно для створення складних структур з кількох контрактів.

Приклад:

type Person = { name: string };
type Employee = { company: string };
type Developer = Person & Employee & { skills: string[] };

const dev: Developer = {
  name: "Alice",
  company: "TechCorp",
  skills: ["TypeScript", "React"]
};

Особливості:

  • Якщо є конфліктні властивості з різними типами → результат може стати never.

  • Добре поєднується з interface і type для композиції.

23. Що таке кортежі (Tuple types) у TypeScript і в яких випадках їх варто застосовувати?

TypeScript

Tuple — це масив із фіксованою кількістю елементів та визначеними типами для кожної позиції. Використовуються, коли порядок і типи елементів наперед відомі.

Приклад:

let user: [number, string, boolean];
user = [1, "Alice", true]; // ✅ правильний порядок і типи
user = ["Alice", 1, true]; // ❌ помилка

Особливості:

  • Можна додати назви для кращої читабельності:
type HttpResponse = [statusCode: number, message: string];
const res: HttpResponse = [200, "OK"];
  • Підтримують optional та rest елементи:
type RGB = [number, number, number?, number?]; // (R, G, B, A?)

Використовувати, коли треба передавати структуровані дані з фіксованим форматом (наприклад, координати, записи логів, HTTP-відповідь).

24. Що таке твердження типів (type assertions) у TypeScript і для чого вони потрібні?

TypeScript

Type assertion — це спосіб сказати компілятору: «повір мені, я знаю реальний тип цього значення». Це не змінює рантайм-поведінку, лише впливає на перевірку типів.

Синтаксис:

let value: unknown = "Hello TS";

// спосіб 1
let strLength: number = (value as string).length;

// спосіб 2 (JSX несумісний, тому рідше)
let strLength2: number = (<string>value).length;

Навіщо це корисно:

  • коли TypeScript не може вивести точний тип;

  • при роботі з any або unknown;

  • при доступі до DOM-елементів:

const input = document.getElementById("username") as HTMLInputElement;
console.log(input.value);

⚠️ Важливо: це не "перетворення типів", а підказка компілятору. Якщо ви помилитеся, помилка проявиться вже у рантаймі.

25. Як працює перевірка типів за допомогою typeof у TypeScript і як її використовувати для type narrowing

TypeScript

typeof у TypeScript використовується для звуження union-типів під час виконання. Це type guard, який дозволяє компілятору зрозуміти, який тип у змінної в конкретній гілці коду.

Приклад:

function printId(id: string | number) {
  if (typeof id === "string") {
    console.log("Uppercase ID:", id.toUpperCase()); // тут id: string
  } else {
    console.log("Numeric ID:", id.toFixed(2)); // тут id: number
  }
}

Особливості:

  • typeof перевіряє типи рантайму: string, number, boolean, object, function, undefined, symbol, bigint.

  • Використовується у функціях для безпечної роботи з union-типами.

Також typeof можна використовувати для отримання типу змінної чи функції при оголошенні:

let person = { name: "Alice", age: 30 };
type Person = typeof person; // { name: string; age: number }
26. Чи можна в TypeScript створювати типи на основі існуючих даних (значень) за допомогою виведення типів?

TypeScript

Так, можна. TypeScript дозволяє виводити типи з існуючих значень за допомогою typeof і keyof.

Приклади:

  1. Отримання типу з об’єкта
const user = {
  id: 1,
  name: "Alice",
  isAdmin: true
};

type User = typeof user;
// User = { id: number; name: string; isAdmin: boolean }
  1. Отримання типів ключів
type UserKeys = keyof typeof user;
// "id" | "name" | "isAdmin"
  1. Комбінація з літеральними типами
const roles = ["admin", "user", "guest"] as const;
type Role = typeof roles[number];
// "admin" | "user" | "guest"

Це дозволяє уникати дублювання коду й гарантує синхронізацію типів з даними.

27. Що таке узагальнені типи (Generics) у TypeScript і для чого вони потрібні?

TypeScript

Generics — це параметризовані типи, які дозволяють писати універсальний і багаторазовий код, зберігаючи типобезпеку. Вони дозволяють відкладати визначення конкретного типу до моменту використання.

Приклад: функція без generics

function identity(value: any): any {
  return value;
}
  • Проблема: втрачається тип.

Приклад з generics

function identity<T>(value: T): T {
  return value;
}

let num = identity<number>(42); // num: number
let str = identity("Hello"); // str: string (TS вивів тип автоматично)

Generics у класах і інтерфейсах

class Box<T> { constructor(public content: T) {}
}

const stringBox = new Box("TS"); // Box<string>
const numberBox = new Box(123); // Box<number>

Навіщо:

  • Писати гнучкий і типобезпечний код (колекції, утиліти, API).

  • Уникати any і втрати інформації про тип.

  • Дозволяє зв’язати вхідний і вихідний типи.

28. Як правильно створити узагальнену (generic) функцію в TypeScript?

TypeScript

Узагальнена функція визначається через параметр типу в кутових дужках <T>. Це дозволяє зберегти типобезпеку і не втрачати інформацію про тип.

Базовий приклад

function identity<T>(value: T): T {
  return value;
}

let n = identity<number>(10); // n: number
let s = identity("TS");       // s: string (тип виведено автоматично)

З кількома параметрами типів

function pair<T, U>(first: T, second: U): [T, U] {
  return [first, second];
}

const result = pair("id", 123); // [string, number]

З обмеженням типу (extends)

function getLength<T extends { length: number }>(item: T): number {
  return item.length;
}

getLength("Hello");       // 5
getLength([1, 2, 3]);     // 3
getLength(42);            // ❌ помилка, бо number не має length

Таким чином, generics роблять функції універсальними, але строго типізованими.

29. Як визначити узагальнені (generic) інтерфейси у TypeScript і для чого вони використовуються?

TypeScript

Узагальнені інтерфейси дозволяють описати контракт, який працює з різними типами, зберігаючи типобезпеку. Для цього в інтерфейс додають параметри типів <T> (або кілька).

Приклад: базовий generic-інтерфейс

interface Box<T> {
  value: T;
}

const numBox: Box<number> = { value: 42 };
const strBox: Box<string> = { value: "Hello" };

З кількома параметрами

interface Pair<K, V> {
  key: K;
  value: V;
}

const pair: Pair<string, number> = { key: "age", value: 30 };

Узагальнені інтерфейси з функціями

interface Repository<T> {
  getAll(): T[];
  getById(id: number): T | null;
}

class UserRepo implements Repository<{ id: number; name: string }> {
  private users = [{ id: 1, name: "Alice" }];
  getAll() { return this.users; }
  getById(id: number) { return this.users.find(u => u.id === id) ?? null; }
}

Навіщо:

  • дозволяють будувати універсальні API (репозиторії, сервіси, колекції);

  • зберігають зв’язок між типами в методах/властивостях;

  • уникання дублювання коду для різних сутностей.

30. Як працюють узагальнені (generic) типи у класах TypeScript і як їх застосовувати?

TypeScript

У TypeScript можна робити класи параметризованими типами, додаючи параметр <T> після імені класу. Це дозволяє створювати універсальні класи, які працюють з різними типами даних, зберігаючи типобезпеку.

Приклад базового generic-класу

class Box<T> {
  constructor(public content: T) {}
  getContent(): T {
    return this.content;
  }
}

const numberBox = new Box<number>(123);
const stringBox = new Box<string>("Hello");

console.log(numberBox.getContent()); // 123
console.log(stringBox.getContent()); // Hello

Клас з кількома параметрами типів

class Pair<K, V> {
  constructor(public key: K, public value: V) {}
}

const pair = new Pair<string, number>("id", 42);

Обмеження generic через extends

class Collection<T extends { id: number }> {
  private items: T[] = [];
  add(item: T) { this.items.push(item); }
  getById(id: number): T | undefined {
    return this.items.find(i => i.id === id);
  }
}

const users = new Collection<{ id: number; name: string }>();
users.add({ id: 1, name: "Alice" });

Переваги:

  • Універсальність класів без втрати типобезпеки.

  • Повторне використання логіки для різних типів.

  • Зв’язок між методами і властивостями через один параметр типу.

31. Як реалізувати узагальнене обмеження (generic constraint) у TypeScript і для чого воно потрібне?

TypeScript

У TypeScript можна обмежити generic-параметр за допомогою extends, щоб він повинен був відповідати певному типу або інтерфейсу. Це дозволяє безпечно використовувати властивості або методи об’єкта всередині функції або класу.

Приклад із функцією

interface HasLength {
  length: number;
}

function logLength<T extends HasLength>(item: T): void {
  console.log(item.length);
}

logLength("Hello");      // ✅ рядок має length
logLength([1, 2, 3]);    // ✅ масив має length
logLength(42);           // ❌ помилка, number не має length

Приклад із класом

class Collection<T extends { id: number }> {
  private items: T[] = [];
  add(item: T) { this.items.push(item); }
  getById(id: number): T | undefined {
    return this.items.find(i => i.id === id);
  }
}

const users = new Collection<{ id: number; name: string }>();
users.add({ id: 1, name: "Alice" }); // ✅ ok

Переваги:

  • Дозволяє використовувати властивості або методи об’єкта без перевірок типу.

  • Зберігає універсальність функцій і класів, але обмежує використання тільки сумісними типами.

32. Що таке дискримінований союз (Discriminated Union) у TypeScript і як він працює?

TypeScript

Discriminated Union — це патерн, коли union типів має спільну властивість-дискримінатор (зазвичай літеральний тип), яка дозволяє компілятору звузити тип під час перевірок.

Приклад:

type Circle = {
  kind: "circle";
  radius: number;
};

type Rectangle = {
  kind: "rectangle";
  width: number;
  height: number;
};

type Shape = Circle | Rectangle;

function area(shape: Shape): number {
  switch (shape.kind) {
    case "circle":
      return Math.PI * shape.radius ** 2;
    case "rectangle":
      return shape.width * shape.height;
  }
}

Особливості:

  • kind (або інша властивість) має літеральне значення, унікальне для кожного варіанту.

  • Це дозволяє TypeScript робити type narrowing автоматично у switch чи if.

  • Використовується для моделювання станів, подій, результатів API.

Фактично, це спосіб реалізації type-safe "enum-like" варіантів з різними структурами даних.

33. Що таке утилітний тип Readonly у TypeScript і як його оголосити/використати?

TypeScript

Readonly<T> — це вбудований утилітний тип, який робить усі властивості об’єкта тільки для читання (неможливо змінювати після ініціалізації).

Приклад використання:

type User = {
  id: number;
  name: string;
};

const u: Readonly<User> = {
  id: 1,
  name: "Alice"
};

u.name = "Bob"; // ❌ Помилка: властивість доступна тільки для читання

Як оголошений всередині TS

type Readonly<T> = {
  readonly [P in keyof T]: T[P];
};
  • Тобто це mapped type, який додає модифікатор readonly до кожної властивості.

Використовується для іммутабельних даних, DTO та запобігання випадковим змінам.

34. Що таке mapped types у TypeScript і як їх використовувати?

TypeScript

Mapped types — це спосіб створювати нові типи на основі існуючих, проходячи по ключах (keyof) та трансформуючи їх. Це використовується для створення утилітних типів (Readonly, Partial, Pick тощо).

Базовий приклад:

type User = {
  id: number;
  name: string;
  active: boolean;
  };

// Робимо всі властивості readonly
type ReadonlyUser = {
  readonly [K in keyof User]: User[K];
};

Використання з модифікаторами:

  • readonly / -readonly → додає або прибирає "тільки для читання"

  • ? / -? → робить поле опціональним або обов’язковим

type PartialUser = {
  [K in keyof User]?: User[K];
};

Generic-приклад:

type MyMapped<T> = {
  [P in keyof T]: T[P];
};

type Test = MyMapped<{ a: string; b: number }>;
// { a: string; b: number }

Реальні приклади (вбудовані утиліти):

  • Readonly<T> → робить усі властивості readonly

  • Partial<T> → робить усі властивості опціональними

  • Required<T> → робить усі властивості обов’язковими

  • Record<K, T> → створює об’єкт, де всі ключі мають значення типу T

Mapped types корисні для масових перетворень типів без дублювання коду.

35. Що таке умовні типи (Conditional Types) у TypeScript і як вони працюють?

TypeScript

Умовні типи дозволяють описувати залежності між типами за допомогою конструкції T extends U ? X : Y.

  • Якщо T підтип U, результат буде X.

  • Інакше — Y.

Базовий приклад:

type IsString<T> = T extends string ? "yes" : "no";

type A = IsString<string>; // "yes"
type B = IsString<number>; // "no"

Використання з узагальненими типами:

type ElementType<T> = T extends (infer U)[] ? U : T;

type A = ElementType<string[]>; // string
type B = ElementType<number>;   // number

Застосування у практиці:

type ApiResponse<T> = T extends Error ? { success: false; error: T }
                                      : { success: true; data: T };

type R1 = ApiResponse<string>; // { success: true; data: string }
type R2 = ApiResponse<Error>;  // { success: false; error: Error }

Особливості:

  • Працюють у поєднанні з generics, union та mapped types.

  • Часто використовуються у вбудованих утилітах:

    • Exclude<T, U>

    • Extract<T, U>

    • NonNullable<T>

Умовні типи — це основа для гнучкої метапрограмінгової типізації.

36. Що таке індексні типи (Indexed Access Types) у TypeScript і як працює ключове слово keyof?

TypeScript

keyof

  • keyof створює об’єднання (union) ключів заданого типу.

  • Використовується для обмеження значень ключами інтерфейсу/типу.

type User = { id: number; name: string; active: boolean };
type UserKeys = keyof User;
// "id" | "name" | "active"

Indexed Access Types (T[K])

  • Дозволяють отримати тип значення за конкретним ключем.
type UserIdType = User["id"]; // number
type UserNameOrActive = User["name" | "active"]; // string | boolean

Приклад разом

function getValue<T, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key];
}

const user: User = { id: 1, name: "Alice", active: true };

let nameValue = getValue(user, "name");   // string
let activeValue = getValue(user, "active"); // boolean

Навіщо це потрібно:

  • Для generic-утиліт, які працюють із довільними об’єктами.

  • Для побудови type-safe доступу до властивостей.

  • Основа для утилітних типів (Pick, Omit, Record тощо).

37. У чому різниця між приведенням типів (type casting) і твердженням типів (type assertion) у TypeScript?

TypeScript

  1. Твердження типів (Type Assertion)
  • Це інструкція для компілятора, що значення має певний тип.

  • Не змінює значення у рантаймі.

  • Використовується, коли розробник краще знає тип, ніж TypeScript.

let value: unknown = "Hello";
let strLength = (value as string).length; // "повір, це string"
  1. Приведення типів (Type Casting, runtime cast)
  • Це перетворення значення в інший тип у рантаймі (наприклад, Number("123") → 123).

  • Виконується реальною функцією чи оператором у JS.

let str = "123";
let num = Number(str); // runtime casting → 123

Відмінність

  • Type assertion: впливає тільки на компіляцію, ніяких змін у рантаймі.

  • Type casting: реально змінює значення під час виконання.

У TypeScript під "casting" часто мають на увазі type assertions, але це не одне й те саме.

38. Що таке утилітні типи Partial, Required, Readonly та Pick у TypeScript і для чого вони потрібні?

TypeScript

  1. Partial<T>
  • Робить усі властивості опціональними.
type User = { id: number; name: string; };
type PartialUser = Partial<User>;
// { id?: number; name?: string }
  • Використовується для об’єктів оновлення/патчів.
  1. Required<T>
  • Робить усі властивості обов’язковими (знімає ?).
type UserOptional = { id?: number; name?: string; };
type RequiredUser = Required<UserOptional>;
// { id: number; name: string }
  • Корисно для валидації, коли потрібен повний об’єкт.
  1. Readonly<T>
  • Робить усі властивості доступними тільки для читання.
type User = { id: number; name: string; };
type ReadonlyUser = Readonly<User>;

const u: ReadonlyUser = { id: 1, name: "Alice" };
u.name = "Bob"; // ❌ Помилка
  • Застосовується для іммутабельних даних.
  1. Pick<T, K>
  • Вибирає підмножину властивостей з типу T.
type User = { id: number; name: string; active: boolean };
type UserPreview = Pick<User, "id" | "name">;
// { id: number; name: string }
  • Корисно для DTO, селекторів, відображення лише потрібних полів.

Усі вони побудовані на mapped types + keyof.

Найчастіше застосовуються для гнучкої типізації API, DTO, form state, патчів даних.

39. Що таке тип never у TypeScript і в яких випадках він застосовується?

TypeScript

never

  • Це спеціальний тип, який означає значення, що ніколи не існує.

  • Використовується там, де функція або вираз не повертає значення взагалі.

Основні випадки використання

  1. Функція, яка ніколи не завершується успішно
function fail(message: string): never {
  throw new Error(message);
}
  1. Функція з нескінченним циклом
function infiniteLoop(): never {
  while (true) {}
}
  1. Exhaustive checking (перевірка вичерпності union-типів)
type Shape = { kind: "circle"; radius: number }
           | { kind: "square"; side: number };

function area(shape: Shape): number {
  switch (shape.kind) {
    case "circle": return Math.PI * shape.radius ** 2;
    case "square": return shape.side ** 2;
    default:
      const _exhaustiveCheck: never = shape; // якщо додати новий варіант → помилка
      return _exhaustiveCheck;
  }
}

Ключові моменти

  • never — підтип будь-якого типу, але жоден тип не є підтипом never (крім нього самого).

  • Використовується для строгих перевірок типів і ситуацій, де значення бути не може.

never корисний у type-safe error handling та для гарантій повного покриття union-типів.

40. Як організувати код за допомогою модулів у TypeScript?

TypeScript

Основи модулів у TypeScript

  • Кожен файл з import або export стає модулем.

  • Використовуються ключові слова export та import (як у ES6).

Приклад організації

math.ts

export function add(a: number, b: number): number {
  return a + b;
}

export const PI = 3.14;

app.ts

import { add, PI } from "./math";

console.log(add(2, 3)); // 5
console.log(PI);        // 3.14

Експорт за замовчуванням

// logger.ts
export default function log(msg: string) {
  console.log("LOG:", msg);
}

// app.ts
import log from "./logger";
log("hello");

Перейменування та групування

import * as MathUtils from "./math";
console.log(MathUtils.add(1, 2));

Організація проекту

  • Файлова структура: групувати код за доменами (наприклад, services/, models/, utils/).

  • Barrel files (індексні модулі): об’єднувати кілька експортувань в одному файлі.

// utils/index.ts
export * from "./math";
export * from "./logger";

// app.ts
import { add, PI } from "./utils";

Конфігурація

  • У tsconfig.json можна налаштувати:

    • "module": (esnext, commonjs, amd, залежно від оточення).

    • "baseUrl", "paths": для зручних alias-імпортів.

{
  "compilerOptions": {
    "baseUrl": "./src",
    "paths": {
      "@utils/*": ["utils/*"]
    }
  }
}

Модулі в TypeScript = ті самі ES6 модулі, але з повною підтримкою типів.

41. У яких випадках доцільно використовувати простори імен (namespace) у TypeScript

TypeScript

Простори імен (namespace)

  • Це спосіб групувати логіку всередині одного глобального об’єкта.

  • Використовувалися до появи модулів для уникнення колізій у глобальному просторі імен.

namespace Utils {
  export function add(a: number, b: number): number {
    return a + b;
  }

  export const PI = 3.14;
}

console.log(Utils.add(2, 3));

Коли можна застосовувати

  1. У legacy-проєктах або коли немає системи модулів (наприклад, код вбудовується напряму в <script> без bundler).

  2. Для простих демо/маленьких проєктів, де немає потреби в модульній структурі.

  3. Для декларацій .d.ts файлів, щоб групувати типи/інтерфейси.

Чому зазвичай не варто

  • У сучасному TypeScript стандартом є ES6 модулі (import/export).

  • Bundlers (Webpack, Vite, esbuild) та Node.js працюють із модулями, а не namespace.

  • Простори імен у великих проєктах ускладнюють масштабування.

Рекомендація:

  • Нові проєкти → використовувати модулі.

  • Простори імен → тільки у специфічних випадках (legacy, declaration merging, глобальні бібліотеки без модулів).

42. У чому різниця між внутрішніми та зовнішніми модулями в TypeScript?

TypeScript

  1. Внутрішні модулі (старий підхід)
  • Використовували ключове слово namespace.

  • Код групується в один глобальний об’єкт.

  • Завантаження відбувається через <script> без системи модулів.

namespace Utils {
  export function add(a: number, b: number) {
    return a + b;
  }
}

console.log(Utils.add(2, 3));

Зараз вважаються застарілими — замінені на ES6-модулі.

  1. Зовнішні модулі (сучасний підхід)
  • Використовують export / import (ES6).

  • Кожен файл із export → модуль.

  • Працюють із bundlers, Node.js, Deno.

// math.ts
export function add(a: number, b: number) {
  return a + b;
}

// app.ts
import { add } from "./math";
console.log(add(2, 3));

Ключова різниця

  • Внутрішні (namespace) → організація всередині одного глобального простору.

  • Зовнішні (modules) → організація через систему файлів з ізольованим простором імен.

На практиці:

  • Використовуємо зовнішні модулі (ES6/TypeScript import/export).

  • Внутрішні модулі (namespace) лишилися тільки для legacy та .d.ts декларацій.

43. Як у TypeScript експортувати та імпортувати модулі?

TypeScript

  1. Іменований експорт
// math.ts
export function add(a: number, b: number): number {
  return a + b;
}
export const PI = 3.14;

// app.ts
import { add, PI } from "./math";
console.log(add(2, 3), PI);
  • Можна імпортувати тільки потрібне.
  1. Експорт за замовчуванням (default)
// logger.ts
export default function log(message: string) {
  console.log("LOG:", message);
}

// app.ts
import log from "./logger";
log("hello");
  • Імпортується без {}, ім’я можна змінювати довільно.
  1. Перейменування при імпорті/експорті
// math.ts
export { add as sum };

// app.ts
import { sum as addNumbers } from "./math";
  1. Імпорт у вигляді простору імен
// app.ts
import * as MathUtils from "./math";
console.log(MathUtils.add(2, 3));
  1. Повторний експорт (re-export)
// utils.ts
export * from "./math";
export { default as log } from "./logger";

// app.ts
import { add, PI, log } from "./utils";

Рекомендація:

  • Використовувати іменований експорт для кількох сутностей.

  • Використовувати default для однієї "головної" сутності з файлу.

44. Що таке роздільна здатність модулів (module resolution) у TypeScript і які існують стратегії?

TypeScript

Роздільна здатність модулів

Це алгоритм, за яким TypeScript знаходить файл, що відповідає шляху з import або require. Приклад:

import { add } from "./math";

TypeScript має зрозуміти, чи це math.ts, math.d.ts, math.js чи інший файл.

Основні стратегії

  1. Classic (старий режим, до ES6)
  • Працює подібно до компілятора C/C++.

  • Використовується для старих скриптів, без node_modules.

  • Пошук іде відносно файлу, де зроблений імпорт.

Використовується рідко, в legacy-коді.

  1. Node (за замовчуванням)
  • Імітує механізм Node.js.

  • Шукає файл у такому порядку:

    • ./module.ts

    • ./module.tsx

    • ./module.d.ts

    • ./module/package.json (types або main)

    • ./module/index.ts

    • ./module/index.d.ts

Використовується у більшості сучасних проєктів.

Вибір стратегії

У tsconfig.json:

{
  "compilerOptions": {
    "moduleResolution": "node" // або "classic"
  }
}

Додаткові можливості

"baseUrl" – вказує базову директорію для відносних шляхів.

"paths" – дозволяє створювати alias-и для імпортів.

{
  "compilerOptions": {
    "baseUrl": "./src",
    "paths": {
      "@utils/*": ["utils/*"]
    }
  }
}
import { add } from "@utils/math";

Підсумок:

Classic – для старих проєктів без модульної системи.

Node – стандарт для сучасних TypeScript/Node.js застосунків.

45. Як модулі TypeScript сумісні з модулями ES6?

TypeScript

Основна ідея

TypeScript повністю базується на ES6-модулях:

  • import / export працюють так само, як у JS.

  • Кожен файл з import або export вважається модулем.

  • Під час компіляції TS може перетворювати код у різні системи модулів (CommonJS, ES6, AMD, UMD тощо).

Приклади

TypeScript

// math.ts
export function add(a: number, b: number): number {
  return a + b;
}

Використання в ES6

import { add } from "./math.js";
console.log(add(2, 3));

Після компіляції з "module": "ESNext" у tsconfig.json результат буде ідентичним ES6.

Сумісність із CommonJS (Node.js)

TypeScript дозволяє імпортувати CommonJS-модулі:

import fs from "fs"; // default-імпорт
import * as path from "path"; // namespace-імпорт

Працює завдяки прапору "esModuleInterop": true у tsconfig.json.

Ключові моменти сумісності

  1. TypeScript → ES6
  • TS просто додає типи, які зникають при компіляції.

  • Залишається чистий ES6-код.

  1. Interop із CommonJS
  • Можна імпортувати старі бібліотеки (require) без проблем.
  1. Default vs Named exports
  • ES6 → default експортується як export default.

  • TS дозволяє змішувати default та named (через esModuleInterop).

Підсумок:

  • TypeScript сумісний із ES6-модулями 1:1.

  • Додатково підтримує CommonJS через компілятор.

  • Використання "module": "ESNext" і "esModuleInterop": true робить код максимально універсальним.

46. Що таке декоратори у TypeScript і як їх використовують?

TypeScript

Визначення

Декоратори — це спеціальні функції, які можна застосовувати до класів, методів, властивостей або параметрів, щоб змінювати або розширювати їхню поведінку. Вони працюють як метадані + синтаксичний цукор над патерном higher-order functions.

  • У TypeScript декоратори — експериментальна функція, вмикаються прапором:
{
  "experimentalDecorators": true,
  "emitDecoratorMetadata": true
}

Синтаксис

function MyDecorator(target: any) {
  console.log("Декоратор застосовано до:", target);
}

@MyDecorator
class Example {}

Види декораторів

  1. Класів
function LogClass(constructor: Function) {
  console.log("Class:", constructor.name);
}

@LogClass
class User {}
  1. Методів
function LogMethod(
  target: any,
  propertyKey: string,
  descriptor: PropertyDescriptor
) {
  const original = descriptor.value;
  descriptor.value = function (...args: any[]) {
    console.log(`Call ${propertyKey} with`, args);
    return original.apply(this, args);
  };
}

class Calculator {
  @LogMethod
  add(a: number, b: number) {
    return a + b;
  }
}

new Calculator().add(2, 3);
  1. Властивостей
function Readonly(target: any, propertyKey: string) {
  Object.defineProperty(target, propertyKey, { writable: false });
}

class Car {
  @Readonly
  brand: string = "Tesla";
}
  1. Параметрів
function LogParam(target: any, method: string, index: number) {
  console.log(`Param at index ${index} in method ${method}`);
}

class Service {
  print(@LogParam msg: string) {
    console.log(msg);
  }
}

Використання на практиці

  • DI-фреймворки (NestJS, Angular) — для позначення сервісів, компонентів.

  • Логування, кешування, валідація.

  • Метадані (через reflect-metadata).

Підсумок:

Декоратори — це функції-обгортки для класів та їх елементів, що дозволяють декларативно додавати поведінку.

47. Що таке декоратори класів у TypeScript і як вони змінюють поведінку класів?

TypeScript

Визначення

Декоратор класу — це функція, яка отримує конструктор класу як аргумент. Він може:

  • додати метадані,

  • змінити або підмінити конструктор,

  • модифікувати/доповнити прототип.

Сигнатура

type ClassDecorator = <TFunction extends Function>(target: TFunction) => TFunction | void;

Приклад 1. Логування створення класу

function LogClass(constructor: Function) {
  console.log(`Клас створено: ${constructor.name}`);
}

@LogClass
class User {}
  • При завантаженні модуля виведе: Клас створено: User.

Приклад 2. Додавання властивості через прототип

function WithTimestamp(constructor: Function) {
  constructor.prototype.timestamp = new Date();
}

@WithTimestamp
class Order {}

const o = new Order();
console.log(o.timestamp); // Дата створення

Приклад 3. Підміна конструктора

function Sealed<T extends { new (...args: any[]): {} }>(constructor: T) {
  return class extends constructor {
    id = Math.random();
  };
}

@Sealed
class Product {
  name = "Book";
}

const p = new Product();
console.log(p.name, p.id); // "Book", 0.12345
  • Декоратор створив новий клас, що розширює оригінальний.

Використання на практиці

  • Angular/NestJS: @Component, @Injectable, @Module.

  • Логування, трейсинг: автоматично додавати поведінку.

  • Метадані: вказувати схеми валідації, ролі доступу тощо.

Підсумок:

Декоратори класів дозволяють декларативно змінювати або розширювати клас (метадані, властивості, конструктор), що робить їх основою для DI та метапрограмування в TypeScript.

48. Що таке декоратори методів у TypeScript і як їх використовувати?

TypeScript

Визначення

Декоратор методу — це функція, яка застосовується до методу класу. Він отримує:

  1. target — прототип класу (для екземплярного методу) або конструктор (для статичного).

  2. propertyKey — ім’я методу.

  3. descriptor — PropertyDescriptor, що описує метод (можна змінювати).

Використовується для перехоплення викликів, логування, кешування, валідації тощо.

Сигнатура

type MethodDecorator = (
  target: Object,
  propertyKey: string | symbol,
  descriptor: PropertyDescriptor
) => void | PropertyDescriptor;

Приклад 1. Логування викликів

function LogMethod(
  target: Object,
  propertyKey: string,
  descriptor: PropertyDescriptor
) {
  const original = descriptor.value;
  descriptor.value = function (...args: any[]) {
    console.log(`Виклик ${propertyKey} з аргументами:`, args);
    return original.apply(this, args);
  };
}

class Calculator {
  @LogMethod
  add(a: number, b: number) {
    return a + b;
  }
}

new Calculator().add(2, 3);
// Лог: "Виклик add з аргументами: [2, 3]"

Приклад 2. Захист від повторних викликів

function Once(target: Object, propertyKey: string, descriptor: PropertyDescriptor) {
  let called = false;
  const original = descriptor.value;
  descriptor.value = function (...args: any[]) {
    if (called) {
      console.log(`Метод ${propertyKey} вже викликано!`);
      return;
    }
    called = true;
    return original.apply(this, args);
  };
}

class Service {
  @Once
  init() {
    console.log("Ініціалізація...");
  }
}

const s = new Service();
s.init(); // "Ініціалізація..."
s.init(); // "Метод init вже викликано!"

Приклад 3. Async error handler

function CatchErrors(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
  const original = descriptor.value;
  descriptor.value = async function (...args: any[]) {
    try {
      return await original.apply(this, args);
    } catch (err) {
      console.error(`Помилка у ${propertyKey}:`, err);
    }
  };
}

class Api {
  @CatchErrors
  async fetchData() {
    throw new Error("Network error");
  }
}

new Api().fetchData(); // Лог: "Помилка у fetchData: Error: Network error"

Підсумок:

Декоратори методів у TypeScript дають можливість переписати або обгорнути метод (через PropertyDescriptor), що робить їх зручними для реалізації AOP-патернів (логування, кешування, обробка помилок, throttle/debounce).

49. Що таке декоратори аксесорів (get/set) у TypeScript і як вони працюють?

TypeScript

Визначення

Декоратори аксесорів застосовуються до геттерів або сеттерів у класах. Вони працюють майже так само, як декоратори методів, але застосовуються до get/set.

  • Сигнатура:
type AccessorDecorator = (
  target: Object,
  propertyKey: string | symbol,
  descriptor: PropertyDescriptor
) => void | PropertyDescriptor;

Приклад 1. Логування доступу

function LogAccessor(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
  const originalGet = descriptor.get;
  const originalSet = descriptor.set;

  if (originalGet) {
    descriptor.get = function () {
      console.log(`Отримання значення ${propertyKey}`);
      return originalGet.apply(this);
    };
  }

  if (originalSet) {
    descriptor.set = function (value: any) {
      console.log(`Присвоєння ${propertyKey} = ${value}`);
      return originalSet.apply(this, [value]);
    };
  }
}

class User {
  private _name: string = "Anonymous";

  @LogAccessor
  get name() {
    return this._name;
  }

  set name(value: string) {
    this._name = value;
  }
}

const u = new User();
console.log(u.name);   // Лог: Отримання значення name
u.name = "Viktor";     // Лог: Присвоєння name = Viktor

Приклад 2. Валідація сеттера

function MinLength(length: number) {
  return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    const originalSet = descriptor.set!;
    descriptor.set = function (value: string) {
      if (value.length < length) {
        throw new Error(`${propertyKey} має бути мінімум ${length} символів`);
      }
      originalSet.call(this, value);
    };
  };
}

class Product {
  private _title: string = "";

  @MinLength(3)
  set title(value: string) {
    this._title = value;
  }

  get title() {
    return this._title;
  }
}

const p = new Product();
p.title = "TV";   // ❌ Error: title має бути мінімум 3 символів

Підсумок

  • Декоратори аксесорів працюють з геттерами/сеттерами.

  • Дозволяють:

    • логувати доступ,

    • робити валідацію,

    • контролювати зміну значень.

  • Як і метод-декоратори, вони змінюють PropertyDescriptor.

50. Що таке декоратори властивостей у TypeScript і як їх використовувати?

TypeScript

Визначення

Декоратор властивості застосовується до поля класу. На відміну від методів чи аксесорів, він не має доступу до PropertyDescriptor, оскільки властивості ще не існують на момент компіляції.

  • Сигнатура:
type PropertyDecorator = (
  target: Object,
  propertyKey: string | symbol
) => void;

Приклад 1. Логування оголошення властивості

function LogProperty(target: any, propertyKey: string) {
  console.log(`Властивість "${propertyKey}" додана у клас ${target.constructor.name}`);
}

class User {
  @LogProperty
  name: string;

  constructor(name: string) {
    this.name = name;
  }
}
// Лог: Властивість "name" додана у клас User

Приклад 2. Додавання метаданих (валидація)

function Required(target: any, propertyKey: string) {
  if (!target.__required) {
    target.__required = [];
  }
  target.__required.push(propertyKey);
}

class Product {
  @Required
  title: string;

  @Required
  price: number;
}

function validate(obj: any) {
  const required = obj.__proto__.__required || [];
  for (const key of required) {
    if (obj[key] === undefined) {
      throw new Error(`Поле ${key} є обов’язковим`);
    }
  }
}

const p = new Product();
p.title = "TV";
validate(p); // ❌ Error: Поле price є обов’язковим

Приклад 3. Автоматична ініціалізація

function DefaultValue(value: any) {
  return function (target: any, propertyKey: string) {
    let val = value;
    Object.defineProperty(target, propertyKey, {
      get: () => val,
      set: (newVal) => (val = newVal),
      enumerable: true,
      configurable: true,
    });
  };
}

class Settings {
  @DefaultValue("light")
  theme: string;
}

const s = new Settings();
console.log(s.theme); // "light"
s.theme = "dark";
console.log(s.theme); // "dark"

Підсумок

  • Декоратори властивостей працюють тільки з назвою властивості та прототипом класу.

  • Використовуються для:

    • логування,

    • додавання метаданих,

    • створення власних валідацій,

    • ініціалізації значень.

  • Для більш складних сценаріїв часто комбінуються з рефлексією (Reflect.metadata) або бібліотеками на кшталт class-validator.

51. Як декоратори допомагають компонувати (організовувати) код у TypeScript?

TypeScript

Визначення

Декоратори — це спеціальні анотації для класів, методів, властивостей чи параметрів, які дозволяють додавати поведінку або метадані, не змінюючи безпосередньо бізнес-логіку. Вони реалізують принципи AOP (Aspect-Oriented Programming) — винесення повторюваних завдань (логування, валідація, DI) в окремі аспекти.

Як саме декоратори компонують код

  1. Виносять повторювану логіку (логування, кешування, валідацію) у незалежні функції.

  2. Роблять код декларативним — замість "писати вручну" можна описати поведінку через анотацію.

  3. Додають метадані до класів/методів/властивостей, які можна зчитувати у runtime.

  4. Сприяють модульності — декоратор можна підключити/відключити без зміни основного коду.

Приклад — логування методів

Без декоратора:

class UserService {
  getUser(id: number) {
    console.log(`Викликано getUser з id=${id}`);
    return { id, name: "Alice" };
  }
}

З декоратором:

function Log(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
  const original = descriptor.value;
  descriptor.value = function (...args: any[]) {
    console.log(`Викликано ${propertyKey} з аргументами:`, args);
    return original.apply(this, args);
  };
}

class UserService {
  @Log
  getUser(id: number) {
    return { id, name: "Alice" };
  }
}
  • Логіка логування винесена окремо, метод залишився чистим.

Приклад — DI (як у Angular / NestJS)

function Injectable(constructor: Function) {
  Reflect.defineMetadata("injectable", true, constructor);
}

@Injectable
class UserService {}

@Injectable
class AuthService {
  constructor(private userService: UserService) {}
}
  • Декоратори дозволяють компонувати сервіси у єдину систему без ручного "склеювання".

Підсумок

  • Декоратори — це механізм композиції коду у TypeScript.

  • Вони дозволяють:

    • підключати поведінку без змін основного коду,

    • додавати метадані для фреймворків (DI, маршрутизація, ORM),

    • робити код чистішим і декларативним.

  • Використовуються в Angular, NestJS, TypeORM, MobX для організації архітектури.

52. Що таке рефлексія у TypeScript і як вона використовується разом із декораторами?

TypeScript

Що таке рефлексія

Рефлексія — це можливість коду отримувати метадані про себе під час виконання (runtime). У TypeScript це реалізується через бібліотеку reflect-metadata, яка дозволяє зчитувати/записувати метадані для класів, методів, властивостей і параметрів.

Як це пов’язано з декораторами

Декоратори не змінюють сам об’єкт напряму, а часто зберігають додаткову інформацію у метаданих. Цю інформацію потім можна зчитати через Reflect.getMetadata.

Приклад — збереження метаданих

import "reflect-metadata";

function MinLength(length: number) {
  return function (target: any, propertyKey: string) {
    Reflect.defineMetadata("minLength", length, target, propertyKey);
  };
}

class User {
  @MinLength(5)
  username: string;
}

// Читання метаданих
const len = Reflect.getMetadata("minLength", User.prototype, "username");
console.log(len); // 5
  • Декоратор @MinLength записує метадані, а валідаційна логіка може потім їх використовувати.

Приклад — декоратори параметрів (DI, як у NestJS)

function Inject(token: string) {
  return function (target: Object, propertyKey: string | symbol, parameterIndex: number) {
    Reflect.defineMetadata("inject", token, target, `param_${parameterIndex}`);
  };
}

class AuthService {
  constructor(
    @Inject("UserService") private userService: any
  ) {}
}

console.log(Reflect.getMetadata("inject", AuthService, "param_0"));
// "UserService"
  • Через рефлексію фреймворк розуміє, який сервіс підставити у конструктор.

Використання у фреймворках

  • NestJS@Controller, @Injectable, @Param, @Body працюють на базі reflect-metadata.

  • TypeORM@Entity, @Column зберігають схему таблиці у метаданих.

  • Angular@Injectable та DI-контейнер також використовують метадані.

Підсумок

  • Рефлексія = механізм зберігання/зчитування метаданих у runtime.

  • Декоратори = зручний спосіб записувати метадані.

  • У поєднанні вони дозволяють будувати декларативні фреймворки (NestJS, Angular, TypeORM), де метадані визначають, як працює DI, маршрутизація чи ORM.

53. Що таке tsconfig.json і для чого він використовується у TypeScript?

TypeScript

Визначення

tsconfig.json — це конфігураційний файл TypeScript, який:

  1. описує як компілювати проєкт (шлях до вихідних файлів, вихідна директорія, таргетовану версію JS тощо);

  2. визначає налаштування компілятора (строгість типів, модульну систему, JSX);

  3. дозволяє інструментам (IDE, build-системам) розуміти структуру проєкту.

Основні поля

{
  "compilerOptions": {
    "target": "ES2020", // версія JS на виході
    "module": "ESNext", // система модулів
    "strict": true, // включає сувору перевірку типів
    "outDir": "./dist", // куди зберігати зкомпільовані файли
    "rootDir": "./src", // де лежить вихідний код
    "esModuleInterop": true, // коректний імпорт CommonJS-модулів
    "skipLibCheck": true // пропускає перевірку *.d.ts
  },
  "include": ["src"], // які файли включати
  "exclude": ["node_modules"] // які ігнорувати
}

Призначення

  • Забезпечує єдині правила компіляції для всіх у проєкті.

  • Дозволяє типізувати код жорсткіше чи м’якше залежно від налаштувань.

  • Дає можливість інтегрувати TypeScript з Webpack, Babel, Jest, ts-node тощо.

Підсумок

tsconfig.json = "контракт" між розробниками, компілятором і tooling’ом про те, як саме треба збирати й перевіряти TypeScript-код.

54. Як у tsconfig.json вказати, які файли включати або виключати під час компіляції?

TypeScript

Ключі для цього

  • include — список файлів/папок, які слід компілювати.

  • exclude — список файлів/папок, які потрібно ігнорувати.

  • files — явний перелік файлів (рідко використовується).

Приклади

  1. Включення всіх файлів із src
{
  "include": ["src"]
}
  1. Включення тільки .ts і .tsx файлів
{
  "include": ["src/**/*.ts", "src/**/*.tsx"]
}
  1. Виключення тестів і node_modules
{
  "include": ["src"],
  "exclude": ["node_modules", "**/*.test.ts"]
}
  1. Використання files для точного списку
{
  "files": ["src/index.ts", "src/app.ts"]
}
  • У такому разі компілюватимуться тільки ці файли, навіть якщо є інші.

Пріоритет

  1. Якщо є files → беруться тільки вони.

  2. Якщо є include → компілятор бере ці файли + всі залежності.

  3. exclude завжди має вищий пріоритет і "вирізає" файли з include.

55. Які найчастіше використовувані параметри в compilerOptions файлі tsconfig.json?

TypeScript

Поширені параметри компілятора

  • target — версія JavaScript на виході
"target": "ES2020"
  • module — система модулів
"module": "ESNext"   // або CommonJS, UMD
  • strict — вмикає всі суворі перевірки типів
"strict": true
  • outDir — директорія для зкомпільованих файлів
"outDir": "./dist"
  • rootDir — коренева папка вихідних файлів
"rootDir": "./src"
  • esModuleInterop — коректний імпорт CommonJS-пакетів
"esModuleInterop": true
  • allowJs — дозволяє компілювати .js файли разом із .ts
"allowJs": true
  • checkJs — перевіряє типи у .js файлах
"checkJs": true
  • sourceMap — створює .map для дебагу в браузері
"sourceMap": true
  • baseUrl + paths — налаштування alias-імпортів
"baseUrl": "./src",
"paths": {
  "@components/*": ["components/*"]
}
  • skipLibCheck — пропускає перевірку типів у *.d.ts
"skipLibCheck": true
  • resolveJsonModule — дозволяє імпортувати .json файли
"resolveJsonModule": true
  • jsx — режим для React/JSX
"jsx": "react-jsx"   // або "react", "preserve"

Підсумок: найчастіше розробники у фронтенді змінюють target, module, strict, outDir, jsx, esModuleInterop, baseUrl/paths.

56. Як TypeScript підтримує source maps і для чого вони потрібні?

TypeScript

Визначення

Source maps — це файли, які дозволяють браузеру або інструментам налагодження зіставляти зкомпільований JavaScript із оригінальним TypeScript-кодом.

  • Формат: .js.map

  • Дозволяє дебагати TS прямо в браузері або IDE, бачачи рядки та колонки TS, а не JS.

Увімкнення у tsconfig.json

{
  "compilerOptions": {
    "sourceMap": true,
    "outDir": "./dist"
  }
}
  • Після компіляції для кожного file.ts з’явиться file.js та file.js.map.

Приклад

// src/app.ts
const msg: string = "Hello TS";
console.log(msg);

При компіляції з sourceMap: true:

  • app.js — згенерований JS

  • app.js.map — мапа для дебагу

  • В браузері можна ставити breakpoints у app.ts, а не у JS.

Робота з інструментами

  • Chrome DevTools / Firefox Debugger — автоматично підхоплюють .map.

  • Webpack / Vite — інтегрують TS source maps у bundle для фронтенду.

  • Node.js (з ts-node або source-map-support) — дебаг TS на сервері.

Корисні параметри

inlineSourceMap: true — вставляє карту прямо в JS файл.

inlineSources: true — вставляє оригінальний TS-код у .map.

{
  "compilerOptions": {
    "inlineSourceMap": true,
    "inlineSources": true
  }
}

Підсумок:

Source maps у TypeScript дозволяють дебагувати оригінальний код після компіляції, зберігаючи зв’язок між TS та JS.

57. Що таке поступова (incremental) збірка в TypeScript і як вона працює?

TypeScript

Визначення

Поступова збірка (Incremental build) — це режим компілятора TypeScript, який дозволяє компілювати тільки ті файли, які змінилися, замість повної перекомпіляції всього проєкту.

  • Зменшує час збірки великих проєктів.

  • Зберігає інформацію про попередню збірку у файлі .tsbuildinfo.

Увімкнення у tsconfig.json

{
  "compilerOptions": {
    "incremental": true,
    "outDir": "./dist"
  }
}
  • При першій компіляції створюється tsconfig.tsbuildinfo.

  • При наступних компіляціях TS перевіряє, які файли змінилися, і компілює лише їх.

Приклад

  1. Проєкт з 1000 файлів.

  2. Змінено тільки 2 файли.

  3. При incremental: true компілятор згенерує JS тільки для цих 2 файлів і оновить .tsbuildinfo.

Додаткові параметри

  • composite: true — потрібен для проєктів з references (Project References).

  • tsBuildInfoFile — можна задати власне ім’я та шлях для .tsbuildinfo.

{
  "compilerOptions": {
    "incremental": true,
    "tsBuildInfoFile": "./.cache/tsbuildinfo"
  }
}

Переваги

  • Значне скорочення часу компіляції на великих проєктах.

  • Сумісність із Project References для багатомодульних систем.

  • Підтримка у CLI (tsc --build) та IDE.

Підсумок:

Поступова збірка дозволяє компілювати лише змінені файли, прискорюючи процес розробки та інтеграцію з багатомодульними проєктами.

58. Що робить параметр компілятора noImplicitAny у TypeScript і як він працює?

TypeScript

Визначення

noImplicitAny — це параметр компілятора, який забороняє TypeScript автоматично підставляти тип any там, де він не вказаний явно.

  • Якщо компілятор не може вивести тип і немає явного типу, він видає помилку.

  • Це допомагає робити код більш типізованим і безпечним.

Увімкнення у tsconfig.json

{
  "compilerOptions": {
    "noImplicitAny": true
  }
}

Приклад 1. Без noImplicitAny

function add(a, b) {
  return a + b;
}
  • a та b автоматично отримують тип any.

  • TypeScript не видає помилку, але типізація відсутня.

Приклад 2. З noImplicitAny: true

function add(a, b) {
  return a + b;
}

// ❌ Помилка: Parameter 'a' implicitly has an 'any' type
  • Тепер потрібно явно вказати типи:
function add(a: number, b: number): number {
  return a + b;
}

Приклад 3. Аргументи callback

[1, 2, 3].map((x) => x * 2); // Без помилки, тип виведено
  • Тут TypeScript може вивести тип (number), тому помилка не з’являється.

Підсумок

  • noImplicitAny: true = сувора політика типів.

  • Запобігає неявному any, роблячи код більш безпечним.

  • Рекомендується завжди увімкати у проєктах, особливо для великих команд.

59. Як увімкнути суворі перевірки на null та undefined у TypeScript?

TypeScript

Визначення

Сувора перевірка на null і undefined допомагає уникати помилок типу Cannot read property of undefined.

  • Параметр strictNullChecks змушує TypeScript розрізняти типи null та undefined від інших типів.

  • Без нього всі типи за замовчуванням можуть бути null/undefined.

Увімкнення у tsconfig.json

{
  "compilerOptions": {
    "strictNullChecks": true
  }
}
  • Альтернатива: увімкнути весь "strict": true — включає strictNullChecks та інші суворі опції.

Приклад

let name: string = "Viktor";
name = null; // ❌ Помилка: Type 'null' is not assignable to type 'string'

let age: number | null = null; // ✅ Допустимо

Використання в функціях

function greet(name: string | null) {
  if (name !== null) {
    console.log("Hello " + name);
  }
}

greet(null); // Коректно
greet("Alice"); // Коректно
  • Тепер TypeScript змушує обробляти можливий null, знижуючи ризик runtime-помилок.

Підсумок

  • strictNullChecks: true → сувора перевірка null/undefined.

  • Рекомендується для безпечнішого і передбачуваного коду.

  • Часто використовується разом із "strict": true для максимальної суворості.

60. Як у TypeScript керувати визначеннями типів (.d.ts) для сторонніх бібліотек?

TypeScript

  1. Бібліотека має власні типи
  • Деякі пакети вже містять .d.ts у собі (наприклад, axios, react).
import axios from "axios";

axios.get<string>("https://api.test"); // працює, бо типи вбудовані
  1. Використання DefinitelyTyped
  • Якщо типів немає в пакеті → інсталюємо окремо:
npm install --save-dev @types/lodash
  • Після цього TS автоматично підхоплює типи.
  1. Створення власних визначень

Якщо немає офіційних або community-типів:

  • Створюємо файл *.d.ts, наприклад custom.d.ts:
declare module "legacy-lib" {
  export function doSomething(input: string): number;
}
  • Тепер можна імпортувати:
import { doSomething } from "legacy-lib";
  1. Тип any як fallback

Якщо типи зовсім невідомі:

declare module "unknown-lib";
  • Усі імпорти з цього модуля будуть типу any.

  • Це останній варіант, коли немає часу писати типи.

  1. Конфігурація пошуку типів

У tsconfig.json можна вказати, де брати типи:

{
  "compilerOptions": {
    "typeRoots": ["./types", "./node_modules/@types"]
  }
}

typeRoots → де шукати визначення.

types → обмежити список типів, які підключаються.

Підсумок

  • Використовуємо вбудовані типи пакета.

  • Якщо їх немає → ставимо @types/….

  • Якщо і там немає → пишемо власні .d.ts.

  • Як fallback → any.

61. Що таке DefinitelyTyped і яке його відношення до TypeScript?

TypeScript

Визначення

DefinitelyTyped — це великий open-source репозиторій GitHub, у якому зберігаються файли визначень типів (.d.ts) для сторонніх JavaScript-бібліотек, які самі по собі не містять типів.

Як це працює

  1. Хтось пише бібліотеку на JS без TypeScript.

  2. Спільнота додає до DefinitelyTyped файл index.d.ts, який описує API цієї бібліотеки.

  3. Ви встановлюєте типи через npm:

npm install --save-dev @types/lodash
  1. TypeScript автоматично знаходить типи й дозволяє безпечно працювати з бібліотекою:
import _ from "lodash";

const nums: number[] = [1, 2, 3];
const doubled = _.map(nums, x => x * 2); // ✅ коректні типи

Взаємозв’язок з TypeScript

  • TypeScript не постачається з типами для всіх JS-бібліотек.

  • DefinitelyTyped заповнює цю прогалину → робить будь-яку популярну JS-бібліотеку "типобезпечною".

  • Визначення типів автоматично інтегруються з TS без додаткових налаштувань.

Підсумок

  • DefinitelyTyped = репозиторій типів для JS-бібліотек.

  • Публікується як пакети @types/*.

  • Дозволяє використовувати бібліотеки без вбудованих типів у TypeScript із повноцінною підтримкою IntelliSense і перевіркою типів.

62. Як у TypeScript використовувати файли типів (@types) з npm?

TypeScript

  1. Якщо бібліотека має вбудовані типи

Багато сучасних пакетів (наприклад, axios, react) уже містять index.d.ts у собі. Додатково нічого ставити не треба:

import axios from "axios";

axios.get<string>("https://api.test");
  1. Якщо бібліотека не має типів

Тоді шукаємо їх у npm-namespace @types:

npm install --save-dev @types/lodash

Тепер у коді:

import _ from "lodash";

const result = _.chunk([1,2,3,4], 2); // тип: number[][]
  1. Якщо немає готових типів у @types

Створюємо власний файл декларацій, наприклад src/types/custom-lib.d.ts:

declare module "custom-lib" {
  export function doSomething(x: string): number;
}

TypeScript автоматично підхопить ці типи (якщо шлях входить у typeRoots або в include у tsconfig.json).

  1. Керування типами через tsconfig.json

За замовчуванням TS підтягує всі типи з node_modules/@types.

Можна обмежити:

{
  "compilerOptions": {
    "types": ["node", "jest"]
  }
}

→ тоді інші типи ігноруються.

Підсумок

  • Вбудовані типи → просто імпортуємо.

  • Через @types → ставимо npm install @types/....

  • Власні типи → створюємо .d.ts.

  • Контроль підключення → через tsconfig.json.

63. Як інтегрувати TypeScript у проєкт Angular?

TypeScript

Відповідь

  1. Angular побудований поверх TypeScript
  • Починаючи з Angular 2+, офіційний фреймворк завжди працює з TS як основною мовою.

  • Angular CLI автоматично генерує tsconfig.json з оптимальними параметрами.

  1. Створення проєкту
npm install -g @angular/cli
ng new my-app

→ CLI налаштує TypeScript, компіляцію та структуру файлів.

  1. Конфігурація TypeScript

У корені буде tsconfig.json, наприклад:

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "ESNext",
    "strict": true,
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true
  }
}
  • experimentalDecorators + emitDecoratorMetadata → потрібні для декораторів Angular (@Component, @Injectable тощо).
  1. Використання TypeScript у коді Angular
  • Класи компонентів і сервісів пишуться на TS:
import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html'
})
export class AppComponent {
  title: string = 'My Angular App';
}
  • Використовуються інтерфейси, дженерики, enum’и для моделей даних.
  1. Типізація сервісів і HTTP-запитів
getUsers(): Observable<User[]> {
  return this.http.get<User[]>('/api/users');
}

Підсумок

  • Angular = нативна інтеграція з TypeScript.

  • Angular CLI автоматично налаштовує TS.

  • TS використовується у компонентах, сервісах, DI, шаблонній перевірці.

  • Основні параметри: strict, experimentalDecorators, emitDecoratorMetadata.

64. Як інтегрувати TypeScript у React-проєкт?

TypeScript

Відповідь

  1. Створення проєкту з TypeScript
  • Використати Next.js:
npx create-next-app@latest
  • Або Vite:
npm create vite@latest my-app -- --template react-ts
  1. Конфігурація tsconfig.json

Приклад базових налаштувань:

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "ESNext",
    "jsx": "react-jsx",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true
  }
}
  1. Типізація компонентів
  • Функціональний компонент:
type Props = { title: string; count?: number };

const Header: React.FC<Props> = ({ title, count }) => (
  <h1>{title} {count}</h1>
);
  • З children:
type Props = { children: React.ReactNode };

const Layout = ({ children }: Props) => <div>{children}</div>;
  1. Хуки з дженериками
const [items, setItems] = useState<string[]>([]);
const ref = useRef<HTMLInputElement>(null);
  1. Типізація подій
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
  console.log(e.target.value);
};
  1. Типи для API / props drilling

Використовуються інтерфейси та типи для моделей даних:

interface User {
  id: number;
  name: string;
}

const UserCard = ({ user }: { user: User }) => <p>{user.name}</p>;

Підсумок

  • React інтегрується з TS через шаблони Next.js або Vite.

  • tsconfig.json має jsx: react-jsx.

  • Компоненти, хуки та події типізуються через інтерфейси, дженерики та утиліти React (React.FC, React.ReactNode).

  • Це підвищує безпеку коду та зручність роботи з пропсами й стейтом.

65. Як інтегрувати TypeScript у Vue.js-проєкт?

TypeScript

  1. Створення Vue + TypeScript проєкту

Зараз офіційний спосіб — Vite + Vue CLI (create-vue):

npm create vue@latest

Під час створення проєкту обираєте Add TypeScript? -> Yes.

  1. Використання .vue з TypeScript

У Vue 3 <script setup> підтримує TypeScript напряму:

<script setup lang="ts">
import { ref } from "vue"

const count = ref<number>(0)

function increment(step: number): void {
  count.value += step
}
</script>

<template>
  <button @click="increment(1)">
    Count: {{ count }}
  </button>
</template>
  1. Типізація пропсів і емісій
<script setup lang="ts">
interface Props {
  msg: string
}
const props = defineProps<Props>()

const emit = defineEmits<{
  (e: "update", value: number): void
}>()
</script>
  1. Типи для композиційного API
import { Ref } from "vue"

function useCounter(initial: number): { count: Ref<number>, inc: () => void } {
  const count = ref(initial)
  const inc = () => count.value++
  return { count, inc }
}
  1. Конфігурація tsconfig.json

Важливі опції:

{
  "compilerOptions": {
    "strict": true,
    "module": "ESNext",
    "target": "ESNext",
    "moduleResolution": "Node",
    "jsx": "preserve",
    "allowJs": false,
    "types": ["vite/client"]
  }
}
  1. Додаткові пакети
  • vue-tsc — статична перевірка .vue файлів

  • @vue/runtime-core — типи Vue API

  • volar (IDE extension) — краща інтеграція TS у Vue

У Vue 2 можна було додавати TypeScript через vue-class-component, але зараз рекомендується Vue 3 + <script setup lang="ts"> — це найпростіший і нативний варіант.

66. Як використовувати TypeScript у Node.js-додатках?

TypeScript

  1. Ініціалізація проєкту
mkdir my-node-ts-app && cd my-node-ts-app
npm init -y
npm install typescript ts-node @types/node --save-dev
npx tsc --init

У tsconfig.json для Node.js найчастіше:

{
  "compilerOptions": {
    "target": "ES2020",
    "module": "CommonJS", // або "ESNext" для ESM
    "moduleResolution": "node",
    "outDir": "./dist",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true
  }
}
  1. Структура проєкту
src/
 ├─ index.ts
 └─ utils.ts
  1. Приклад коду з типами
// src/index.ts
import { readFileSync } from "fs"

function readConfig(path: string): Record<string, unknown> {
  const content = readFileSync(path, "utf-8")
  return JSON.parse(content)
}

console.log(readConfig("./config.json"))
  1. Запуск

Прямо через ts-node (зручно для девелопменту):

npx ts-node src/index.ts

З компіляцією в JS (продакшн):

npx tsc
node dist/index.js
  1. Використання з Express
npm install express
npm install @types/express --save-dev
import express, { Request, Response } from "express"

const app = express()

app.get("/", (req: Request, res: Response) => {
  res.send("Hello, TS + Node.js")
})

app.listen(3000, () => console.log("Server running"))
  1. Переваги TypeScript у Node.js
  • Безпечні типи для API (Express, FS, DB).

  • Зручні автопідказки в IDE.

  • Краща підтримка великих кодових баз.

  • Легше уникати runtime-помилок.

67. Які переваги використання статичного аналізатора коду (TSLint або ESLint з TypeScript-плагіном)?

TypeScript

Переваги:

  1. Єдиний кодстайл — забезпечує консистентність у команді (наприклад, лапки ' vs ").

  2. Раннє виявлення помилок — ловить некоректні конструкції ще до запуску (невикористані змінні, неправильні імпорти).

  3. Краща читабельність — автоматичне форматування та правила полегшують підтримку коду.

  4. Інтеграція з IDE/CI — помилки видно під час розробки, а також можна блокувати комміти/білди з помилками.

  5. Безпека — правила допомагають уникати небезпечних практик (наприклад, any або eval).

  6. Автоматичні виправлення — більшість проблем виправляється командою eslint --fix.

⚠️ TSLint офіційно deprecated (з 2019), рекомендовано використовувати ESLint + @typescript-eslint/parser.

68. Як налаштувати збірку проєкту на TypeScript за допомогою Webpack?

TypeScript

Крок 1. Встановлення залежностей

npm install --save-dev webpack webpack-cli webpack-dev-server \
  typescript ts-loader source-map-loader \
  html-webpack-plugin clean-webpack-plugin

Крок 2. Ініціалізація TypeScript

npx tsc --init

У tsconfig.json важливо:

{
  "compilerOptions": {
    "target": "ES6",
    "module": "ESNext",
    "moduleResolution": "node",
    "outDir": "./dist",
    "sourceMap": true,
    "strict": true,
    "esModuleInterop": true
  },
  "include": ["src"]
}

Крок 3. Структура проєкту

project/
 ├─ src/
 │   └─ index.ts
 ├─ dist/
 ├─ tsconfig.json
 └─ webpack.config.js

Крок 4. Налаштування Webpack

// webpack.config.js
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");

module.exports = {
  entry: "./src/index.ts",
  module: {
    rules: [
      {
        test: /\.ts$/,
        use: "ts-loader",
        exclude: /node_modules/,
      },
      {
        enforce: "pre",
        test: /\.js$/,
        loader: "source-map-loader",
      },
    ],
  },
  resolve: {
    extensions: [".ts", ".js"],
  },
  output: {
    filename: "bundle.js",
    path: path.resolve(__dirname, "dist"),
  },
  devtool: "source-map",
  plugins: [
    new CleanWebpackPlugin(),
    new HtmlWebpackPlugin({
      template: "src/index.html",
    }),
  ],
  devServer: {
    static: "./dist",
    hot: true,
    port: 3000,
  },
};

Крок 5. Сценарії npm

"scripts": {
  "build": "webpack --mode production",
  "start": "webpack serve --mode development"
}

В результаті:

  • npm start запускає dev-server з TypeScript і hot reload.

  • npm run build створює оптимізований бандл у dist/.

69. Як інтегрувати TypeScript з Babel і в чому різниця від використання tsc?

TypeScript

Принцип роботи

  • TypeScript Compiler (tsc): компілює TS → JS і перевіряє типи.

  • Babel: трансформує TS-синтаксис → JS, але ігнорує типи (типи видаляються на етапі компіляції).

Тобто:

  • Якщо потрібна лише підтримка синтаксису (наприклад, у React), достатньо Babel.

  • Якщо потрібна перевірка типів, треба додатково запускати tsc --noEmit або використовувати fork-ts-checker-webpack-plugin.

Налаштування Babel з TypeScript

  1. Залежності
npm install --save-dev @babel/core @babel/preset-env @babel/preset-typescript \
  @babel/preset-react
  1. .babelrc
{
  "presets": [
    "@babel/preset-env",
    "@babel/preset-react",
    "@babel/preset-typescript"
  ]
}
  1. package.json scripts
"scripts": {
  "build": "babel src --extensions \".ts,.tsx\" --out-dir dist",
  "type-check": "tsc --noEmit"
}

Коли вибирати Babel?

  • Проєкт на React з сучасними фічами, які Babel трансформує краще за tsc.

  • Потрібна інтеграція з Webpack/Rollup/Vite.

  • Хочеш швидшу збірку (Babel швидший, але без type-check).

Коли вибирати tsc?

  • Якщо потрібен чистий TS-проєкт без зайвих тулів.

  • Якщо важлива строга перевірка типів у компіляції.

На практиці часто використовують гібридний підхід:

  • Babel робить трансформацію коду.

  • tsc --noEmit або fork-ts-checker-webpack-plugin робить перевірку типів.

70. Як TypeScript інтегрується з Visual Studio Code та іншими IDE?

TypeScript

Використання з VS Code

  • Вбудована підтримка: VS Code вже має TypeScript Language Service.

  • Автодоповнення (IntelliSense): пропонує методи, змінні, типи в реальному часі.

  • Навігація по коду: Go to Definition, Peek Definition, Find References.

  • Перевірка типів на льоту: IDE показує помилки ще до компіляції.

  • Refactoring tools: перейменування змінних, витягування інтерфейсів, зміна сигнатур функцій.

  • Source Maps інтеграція: зручний дебаг у браузері/Node.js.

  • tsconfig.json awareness: VS Code читає конфігурацію проєкту й підлаштовує підсвічування.

Інші IDE (WebStorm, IntelliJ, Eclipse, Vim/Neovim)

  • Використовують TypeScript Language Server (tsserver).

  • Дають аналогічний набір можливостей: IntelliSense, типізація, refactor, linting.

  • Перевага WebStorm/IntelliJ — вбудовані інструменти для Angular/React, але VS Code легший і гнучкіший через плагіни.

✅ Висновок:

  • TypeScript максимально ефективний у VS Code, бо Microsoft розробляє і сам TypeScript, і VS Code.

  • У інших IDE можливості теж доступні, але VS Code зазвичай оновлюється швидше й краще інтегрований.

71. Які best practices для організації коду та структурування TypeScript-застосунку?

TypeScript

Основні принципи структурування

  1. Використовуй tsconfig.json для централізованих налаштувань
  • strict: true → строгі перевірки.

  • baseUrl + paths → заміна відносних імпортів (../../../) на зрозумілі alias-и.

  1. Організація папок
src/
  components/     // UI або бізнес-компоненти
  services/       // API, робота з даними
  models/         // інтерфейси та типи
  utils/          // хелпери
  hooks/          // кастомні React hooks
  config/         // налаштування
  index.ts        // точка входу
  • Кожен модуль експортує лише те, що потрібно (через index.ts у папці).
  1. Використовуй інтерфейси/типи для контрактів
  • interface для моделей даних та API.

  • type для Union/Intersection.

  1. Сервіси й утиліти мають бути незалежні від UI
  • Розділяй бізнес-логіку (services, models) і UI-логіку (components).
  1. Використовуй Barrel exports (index.ts)
// src/services/index.ts
export * from './authService';
export * from './userService';
  1. Розділяй типи в окрему папку types/
  • Добре для спільних моделей (User, Product).

  • Використовуй @types із npm, якщо є.

  1. Використовуй ESLint + Prettier з правилами для TS
  • Забезпечує єдиний стиль коду.

  • Ловить помилки раніше компіляції.

  1. Type-safe API-клієнти
  • Використовуй axios/fetch з власними типами респонсів.

  • Винось DTO (Data Transfer Objects) окремо.

  1. Ніколи не вимикай строгі прапорці
  • noImplicitAny, strictNullChecks, noUnusedLocals.
  1. Використовуй async/await з типами
  • Обгортай відповіді від API у Promise.

✅ Висновок:

  • Правильна структура TS-проєкту = чітке розділення шарів (UI / бізнес / дані), строга типізація, єдиний стиль коду, мінімізація "any".
72. Як правильно використовувати async/await у TypeScript?

TypeScript

Основи

  • async → функція завжди повертає Promise<T>.

  • await → зупиняє виконання до завершення Promise.

async function fetchUser(id: number): Promise<User> {
  const res = await fetch(`/api/users/${id}`);
  return res.json() as Promise<User>;
}

Обробка помилок

Використовуй try/catch:

async function safeFetch(): Promise<void> {
  try {
    const data = await fetchUser(1);
    console.log(data);
  } catch (err: unknown) {
    if (err instanceof Error) {
      console.error(err.message);
    }
  }
}

Паралельні виклики

async function loadData() {
  const [users, posts] = await Promise.all([
    fetchUsers(),
    fetchPosts()
  ]);
}

Типізація результатів

Функція повинна мати чіткий тип повернення:

async function getNumber(): Promise<number> {
  return 42;
}

✅ Висновок:

  • У TypeScript async/await = асинхронний код із чіткою типізацією результатів.

  • Завжди використовуй try/catch, або обробку через .catch() при Promise.all.

73. Які підходи до керування станом у TypeScript-застосунках і як їх правильно типізувати?

TypeScript

Локальний стан (React / Vue / Angular)

  • Використовуй узагальнені хуки/сервіси з чіткими типами.
const [count, setCount] = useState<number>(0);

Глобальний стан

  1. Redux Toolkit + TypeScript
  • Типізація RootState, AppDispatch.

  • Використовуй createSlice, щоб уникати boilerplate.

interface CounterState { value: number }
const initialState: CounterState = { value: 0 };
  1. MobX
  • Класи з observable + інтерфейси.

  • Просте зв’язування з TS через декоратори.

  1. Context API (React)
  • Визначай інтерфейси для значень контексту.
interface AuthContext {
  user: User | null;
  login: (u: User) => void;
}

Сторонні менеджери стану

  • Zustand → легкий синтаксис + TS дружній.

  • Recoil → атоми зі строгими типами.

  • XState → finite-state machines з автоматичною типізацією переходів.

Best Practices

  • Завжди визначай інтерфейси/типи стану (interface State { ... }).

  • Використовуй enum для action type або as const для уникнення помилок у рядках.

  • Використовуй типобезпечні селектори ((state: RootState) => state.user).

  • Мінімізуй глобальний стан → тримай лише дані, які реально потрібні в багатьох місцях.

✅ Висновок:

  • TypeScript робить керування станом більш безпечним: ти отримуєш автодоповнення, контроль над структурою стану та уникнення runtime-помилок.
74. Які шаблони проектування найчастіше застосовують у TypeScript?

TypeScript

Створюючі (Creational)

  1. Singleton – один екземпляр класу (наприклад, конфігурація, логгер).
class Logger {
  private static instance: Logger;
  private constructor() {}
  static getInstance() {
    if (!Logger.instance) Logger.instance = new Logger();
    return Logger.instance;
  }
}
  1. Factory Method / Abstract Factory – створення об’єктів через фабрику з типами.

Структурні (Structural)

  1. Adapter – адаптація інтерфейсу під інший.
interface Payment { pay(amount: number): void; }
class PayPal { makePayment(sum: number) { /* ... */ } }
class PayPalAdapter implements Payment {
  constructor(private paypal: PayPal) {}
  pay(amount: number) { this.paypal.makePayment(amount); }
}
  1. Decorator – динамічно додає поведінку до класу.

  2. Proxy – контроль доступу або кешування.

Поведінкові (Behavioral)

  1. Observer – підписка/відписка (події, реактивність).
type Listener = (data: string) => void;
class EventEmitter {
  private listeners: Listener[] = [];
  subscribe(l: Listener) { this.listeners.push(l); }
  emit(data: string) { this.listeners.forEach(l => l(data)); }
}
  1. Strategy – змінюваний алгоритм (наприклад, різні способи валідації).

  2. Command – інкапсуляція дії у вигляді об’єкта.

  3. Mediator – координація взаємодії між об’єктами.

Переваги в TypeScript

  • Інтерфейси та дженерики роблять шаблони більш строгими.

  • Decorators (експериментальні) дозволяють реалізовувати Decorator, DI та Aspect-Oriented Programming.

  • Union/Discriminated Unions часто спрощують реалізацію State чи Strategy.

75. Які основні підходи для налагодження TypeScript-застосунків?

TypeScript

Кроки для відлагодження

  1. Source Maps
  • У tsconfig.json додати:
{
  "compilerOptions": {
    "sourceMap": true
  }
}
  • Це дозволяє дебагеру (Chrome DevTools, VS Code) показувати оригінальний .ts код замість згенерованого .js.
  1. Debug у VS Code
  • Налаштувати launch.json:
{
  "version": "0.2.0",
  "configurations": [
    {
      "type": "node",
      "request": "launch",
      "name": "Debug TS",
      "program": "${workspaceFolder}/src/index.ts",
      "preLaunchTask": "tsc: build - tsconfig.json",
      "outFiles": ["${workspaceFolder}/dist/**/*.js"]
    }
  ]
}
  • Ставиш брейкпоінти прямо у TypeScript-файлах.
  1. Debug у браузері
  • Використовувати webpack або vite з підтримкою source maps.

  • Відкрити Chrome DevTools → вкладка Sources → знаходиш .ts файл → ставиш брейкпоінт.

  1. Консольні інструменти
  • console.log, console.table, debugger працюють так само, але з відображенням через source maps.
  1. Node.js Debugging
  • Запуск з Node.js:
node --inspect-brk dist/index.js
  • Підключаєшся через Chrome DevTools або VS Code.
  1. Unit-тести з дебагом
  • Якщо використовуєш Jest / Mocha → можна запускати з --inspect.

  • Це допомагає відслідковувати помилки ще до рантайму.

✅ Ключ: завжди компілюй з sourceMap і налагоджуй оригінальний .ts код, а не згенерований .js.

76. Як писати модульні тести для TypeScript-коду?

TypeScript

Основні кроки

  1. Вибір тестового фреймворку
  • Найчастіше: Jest, Mocha + Chai, Vitest.

  • Для фронтенду (React/Angular): зазвичай Jest.

  1. Налаштування TypeScript
  • Встановлення залежностей (на прикладі Jest):
npm install --save-dev jest ts-jest @types/jest
  • Ініціалізація:
npx ts-jest config:init
  1. Приклад функції (яку тестуємо):
// src/utils/math.ts
export function add(a: number, b: number): number {
  return a + b;
}
  1. Приклад тесту
// tests/math.test.ts
import { add } from "../src/utils/math";

test("adds two numbers", () => {
  expect(add(2, 3)).toBe(5);
});
  1. Запуск тестів
npx jest
  1. Типізація у тестах
  • Використовуєш ті самі TS-типи.

  • Якщо треба mock-и → jest.mock або ts-mockito.

  1. Кращі практики
  • Тести зберігати в __tests__/ або *.test.ts.

  • Перевіряти і позитивні, і негативні кейси.

  • Використовувати beforeEach / afterEach для підготовки оточення.

  • Перекривати тільки те, що потрібно (не мокати все без потреби).

✅ Резюме: модульні тести в TypeScript пишуться так само, як у JavaScript, але з додатковими перевагами типізації. Найзручніше — Jest + ts-jest.

77. Які тестові фреймворки зазвичай застосовують для TypeScript-проєктів?

TypeScript

Найпопулярніші

  1. Jest
  • Найпоширеніший для фронтенду та Node.js.

  • Підтримує TypeScript через ts-jest.

  • Вбудовані мокі, асинхронні тести, snapshot testing.

  1. Vitest
  • Швидкий, сучасний, інтегрується з Vite.

  • Підтримує TS нативно.

  • API сумісний із Jest.

  1. Mocha + Chai
  • Класичний стек для Node.js.

  • ts-node дозволяє запускати тести на TS без компіляції.

  • Чудово підходить для серверних застосунків.

  1. AVA
  • Мінімалістичний, паралельне виконання тестів.

  • Підтримка TS через ts-node/register.

  1. Jasmine
  • Використовувався для Angular, можна писати тести на TS.

  • Менш популярний сьогодні через Jest/Vitest.

78. Як виконувати наскрізне (E2E) тестування у TypeScript-застосунках?

TypeScript

Основні інструменти

  1. Cypress
  • Дуже популярний для фронтенду.

  • Підтримує TypeScript через tsconfig.json.

  • Вбудований девтулс, мок-сервер, зручні API для селекторів.

  1. Playwright
  • Кросбраузерне тестування (Chromium, Firefox, WebKit).

  • TypeScript підтримується "з коробки".

  • Можна тестувати UI, API, мобільні емуляції.

  1. Puppeteer
  • Автоматизація браузера Chrome.

  • TypeScript через npm типи (@types/puppeteer).

Налаштування TypeScript для E2E

  1. tsconfig.json
{
  "compilerOptions": {
    "target": "ES6",
    "module": "CommonJS",
    "strict": true,
    "esModuleInterop": true,
    "types": ["cypress", "node"]
  },
  "include": ["cypress/**/*.ts"]
}
  1. Cypress приклад тесту на TS
describe('Login page', () => {
  it('should log in successfully', () => {
    cy.visit('/login');
    cy.get('input[name="email"]').type('[email protected]');
    cy.get('input[name="password"]').type('password123');
    cy.get('button[type="submit"]').click();
    cy.url().should('include', '/dashboard');
  });
});
  1. Playwright приклад
import { test, expect } from '@playwright/test';

test('login test', async ({ page }) => {
  await page.goto('http://localhost:3000/login');
  await page.fill('input[name=email]', '[email protected]');
  await page.fill('input[name=password]', 'password123');
  await page.click('button[type=submit]');
  await expect(page).toHaveURL(/dashboard/);
});

Best practices

  • Типізувати тести і селектори через інтерфейси або константи.

  • Використовувати Page Object Model (POM) для організації коду тестів.

  • Писати окремі тести для функціональних сценаріїв, а не перевіряти все одним тестом.

  • Інтегрувати E2E у CI/CD (GitHub Actions, GitLab CI) з відловом помилок.

✅ Висновок: TypeScript + E2E тести дозволяють мати типобезпечні, надійні та підтримувані тести для UI і API.

  • Cypress → швидкий для фронтенду.

  • Playwright → кросбраузерна автоматизація.

79. Як налаштувати і використовувати TypeScript з Express.js?

TypeScript

  1. Ініціалізуєш проект:
npm init -y
npm install express
npm install -D typescript ts-node @types/node @types/express
  1. Створюєш tsconfig.json (базово з npx tsc --init).

  2. Пишеш код у src/index.ts:

import express, { Request, Response } from "express";

const app = express();

app.get("/", (req: Request, res: Response) => {
  res.send("Hello TypeScript + Express!");
});

app.listen(3000, () => console.log("Server running on port 3000"));
  1. Запускаєш у дев-режимі:
npx ts-node src/index.ts
  1. Для продакшена — компілюєш (npx tsc) і запускаєш node dist/index.js.

Цього мінімуму достатньо, щоб підняти Express-сервер на TypeScript.

80. Як створювати RESTful сервіси з використанням TypeScript?

TypeScript

  1. Архітектура
  • Використовують Express/Koa/NestJS як HTTP-фреймворк.

  • Розділяють шари: routes → controllers → services → repositories.

  • Використовують DTO/інтерфейси для типобезпеки.

  1. Маршрути
  • Описують ендпоінти (CRUD).

  • Типізують Request, Response.

  • Використовують middleware для аутентифікації, валідації.

  1. Валідація та типізація
  • zod, class-validator або joi для перевірки вхідних даних.

  • Інтерфейси/типи для моделей.

  1. Документація
  • Swagger/OpenAPI для автогенерації документації.
  1. Приклад мінімального REST-контролера (Express + TS):
import { Router, Request, Response } from "express";
const router = Router();

interface User {
  id: number;
  name: string;
}

let users: User[] = [];

router.get("/users", (req: Request, res: Response) => {
  res.json(users);
});

router.post("/users", (req: Request, res: Response) => {
  const newUser: User = { id: Date.now(), ...req.body };
  users.push(newUser);
  res.status(201).json(newUser);
});

export default router;

Ключова перевага TS — чітка типізація моделей та DTO, що знижує ризик помилок у великих REST API.

81. Як організувати моделювання даних у бекенді на TypeScript?

TypeScript

  1. Інтерфейси та типи
  • Використовуються для опису доменних моделей (User, Product, Order).

  • Забезпечують статичну перевірку відповідності даних.

  1. Класи / DTO
  • Використовуються для передачі даних між шарами (DTO: Data Transfer Object).

  • Можуть мати валідацію (наприклад, через class-validator).

  1. ORM/ODM інтеграція
  • З TypeScript добре працюють:

  • TypeORM / Prisma (SQL)

  • Mongoose (MongoDB)

  • Вони генерують типи моделей з бази або навпаки — з TS-кодів.

  1. Валідація + серіалізація
  • zod, yup, class-validator для перевірки схем.

  • Це дає гарантію, що дані з клієнта відповідають моделі.

  1. Приклад моделі (Prisma + TS):
// schema.prisma
model User {
  id    Int     @id @default(autoincrement())
  email String  @unique
  name  String?
}

// user.service.ts
import { PrismaClient } from "@prisma/client";
const prisma = new PrismaClient();

export async function createUser(email: string, name?: string) {
  return prisma.user.create({ data: { email, name } });
}

Основна ідея: моделі у TypeScript описують доменні сутності, поєднуються з ORM для роботи з БД і доповнюються валідацією для захисту від некоректних даних.

82. Які підходи є для роботи з базами даних у бекенді на TypeScript і як ними керувати?

TypeScript

  1. Рівні взаємодії
  • SQL-запити напряму → pg, mysql2, mssql драйвери. Гнучко, але більше boilerplate.

  • Query builders → Knex.js, Kysely. Дає типобезпеку і менш громіздкі запити.

  • ORM/ODM → Prisma, TypeORM, Sequelize, Mongoose (для MongoDB). Автоматично генерують моделі, типи, відношення.

  1. Організація коду
  • Створюють шар repository для роботи з БД.

  • Сервіси звертаються до репозиторіїв, а не напряму до драйвера.

  • Використовують DTO/інтерфейси для повернення типізованих результатів.

  1. Міграції та схеми
  • Prisma/TypeORM мають вбудовані інструменти для міграцій.

  • Забезпечує контроль версій БД.

  1. Приклад (Prisma + TS):
import { PrismaClient } from "@prisma/client";
const prisma = new PrismaClient();

// Repository
export async function getUserById(id: number) {
  return prisma.user.findUnique({ where: { id } });
}

export async function createUser(email: string, name?: string) {
  return prisma.user.create({ data: { email, name } });
}

Ключова ідея: у TypeScript завжди прагнемо до типобезпеки — тому ORM/Query Builder + інтерфейси/DTO найкраще підходять для керованої взаємодії з БД.

83. Які стратегії автентифікації та авторизації застосовуються в TypeScript API?

TypeScript

  1. Автентифікація (Authentication)
  • JWT (JSON Web Token) → найпоширеніший варіант. Токен зберігається в Authorization: Bearer <token>.

  • Session + Cookies → через express-session, часто з Redis. Підходить для веб-додатків.

  • OAuth2 / OpenID Connect → інтеграція з Google, GitHub, Microsoft.

  • API Keys → для сервіс-ту-сервіс інтеграцій.

  1. Авторизація (Authorization)
  • Role-based (RBAC) → ролі (admin, user, manager).

  • Permission-based (PBAC) → більш гранульований контроль доступу.

  • Attribute-based (ABAC) → правила на основі атрибутів користувача й ресурсу.

  1. Практики реалізації в TypeScript API
  • Middleware для перевірки токенів (наприклад, express-jwt, passport-jwt).

  • Використання DTO для типізації req.user.

  • Зберігання секретів у Vault/AWS Secrets Manager, а не в коді.

  1. Приклад (JWT + Express + TS):
import jwt from "jsonwebtoken";
import { Request, Response, NextFunction } from "express";

interface JwtPayload { userId: number; role: string; }

export function authMiddleware(req: Request, res: Response, next: NextFunction) {
  const token = req.headers.authorization?.split(" ")[1];
  if (!token) return res.status(401).json({ error: "Unauthorized" });

  try {
    const payload = jwt.verify(token, process.env.JWT_SECRET!) as JwtPayload;
    (req as any).user = payload;
    next();
  } catch {
    res.status(403).json({ error: "Invalid token" });
  }
}

Основна ідея:

  • Authentication = підтвердити, хто користувач.

  • Authorization = вирішити, що він може робити.

У TypeScript API все робиться через middleware + чітку типізацію req.user.

84. Як застосовувати TypeScript у підході Domain-Driven Design (DDD)?

TypeScript

  1. Основні принципи DDD у TS
  • Domain layer: інтерфейси, value objects, entities → чисті TS-типи та класи.

  • Application layer: сервіси/юзкейси, які координують доменну логіку.

  • Infrastructure layer: робота з БД, API, сторонні сервіси (реалізації інтерфейсів).

  • Presentation layer: контролери/REST/GraphQL, які викликають application layer.

  1. Типобезпека як перевага
  • Value objects описуються через класи або типи (наприклад, Email, Price).

  • Використання readonly і private захищає інваріанти.

  • DTO/інтерфейси забезпечують чітку межу між шарами.

  1. Приклад сутності (Entity + Value Object):
// Value Object
export class Email {
  private readonly value: string;
  constructor(value: string) {
    if (!/^[\w.-]+@[\w.-]+\.\w+$/.test(value)) {
      throw new Error("Invalid email format");
    }
    this.value = value;
  }
  getValue(): string {
    return this.value;
  }
}

// Entity
export class User {
  constructor(
    public readonly id: number,
    private _email: Email,
    private _name: string
  ) {}

  changeEmail(newEmail: Email) {
    this._email = newEmail;
  }
}
  1. Repositories
  • Визначаються як інтерфейси у домені:
export interface UserRepository {
  findById(id: number): Promise<User | null>;
  save(user: User): Promise<void>;
}
  • Реалізація у infrastructure (ORM/DB).
  1. Application Services (Use Cases)
  • Координують бізнес-логіку без знання деталей інфраструктури.

Головна ідея: TypeScript дає строгу типізацію, що ідеально лягає на концепцію DDD — чіткі доменні моделі, інваріанти, розділення відповідальностей.

85. Як реалізувати пошук подій у бекенді на TypeScript?

TypeScript

  1. Моделювання сутності "Подія" (Event)
  • Використати інтерфейс або ORM-модель:
interface Event {
  id: number;
  title: string;
  date: Date;
  location: string;
  tags?: string[];
}
  1. Джерело даних
  • Якщо база SQL → Prisma/TypeORM (WHERE title ILIKE %...%).

  • Якщо NoSQL (MongoDB) → фільтрація через find({ title: /regex/ }).

  1. API-ендпоінт для пошуку
import { Request, Response } from "express";
import { prisma } from "../prisma"; // приклад із Prisma

export async function searchEvents(req: Request, res: Response) {
  const { query, date, location } = req.query;

  const events = await prisma.event.findMany({
    where: {
      title: query ? { contains: String(query), mode: "insensitive" } : undefined,
      date: date ? new Date(String(date)) : undefined,
      location: location ? String(location) : undefined,
    },
  });

  res.json(events);
}
  1. Покращення пошуку
  • Індекси в БД (title, date).

  • Повнотекстовий пошук (Postgres tsvector, Elasticsearch, Meilisearch).

  • Пагінація (skip, take або LIMIT, OFFSET).

Основна ідея: в TypeScript пошук подій — це типізована модель + query-логіка (ORM/DB) + REST/GraphQL endpoint.

86. Що таке ізоморфні (універсальні) додатки в контексті TypeScript і як вони працюють?

TypeScript

  1. Визначення
  • Ізоморфний (універсальний) додаток — це застосунок, де один і той самий код (зазвичай написаний на TypeScript/JavaScript) виконується і на сервері, і на клієнті.

  • Найчастіше йдеться про рендеринг UI: сервер віддає HTML, а клієнт "гідратує" його і підхоплює подальшу логіку.

  1. Технологічна основа
  • React + TypeScript → Next.js — типовий приклад ізоморфного фреймворку.

  • Код компонентів пишеться на TS, використовується і для SSR (server-side rendering), і для CSR (client-side rendering).

  1. Переваги
  • Кращий SEO завдяки SSR.

  • Швидший TTFB (користувач одразу бачить контент).

  • Повторне використання типів/бізнес-логіки між фронтендом і бекендом.

  1. Приклад ізоморфного підходу (Next.js + TS):
// pages/index.tsx
import { GetServerSideProps } from "next";

interface HomeProps {
  serverTime: string;
}

export default function Home({ serverTime }: HomeProps) {
  return <h1>Server time: {serverTime}</h1>;
}

export const getServerSideProps: GetServerSideProps = async () => {
  return { props: { serverTime: new Date().toISOString() } };
};
  • Тут getServerSideProps виконується на сервері (Node.js), а компонент Home відмальовується на клієнті, використовуючи спільну типізацію TS.

Суть: ізоморфні додатки на TypeScript дозволяють мати єдину кодову базу для бізнес-логіки, моделей і UI, що працюють як на клієнті, так і на сервері.

87. Як TypeScript підтримує та реалізує останні стандарти ECMAScript?

TypeScript

  1. Суперсет JavaScript
  • TypeScript — це надбудова над JS, тому він підтримує всі сучасні можливості ECMAScript (ES6+).

  • Ви можете писати async/await, destructuring, classes, modules, optional chaining, nullish coalescing тощо.

  1. Компільований код
  • TS транслює сучасний синтаксис ES у код, сумісний з цільовою версією JS (target у tsconfig.json), наприклад:

  • target: "ES5" → транскомпіляція для старих браузерів.

  • target: "ES2022" → мінімальна трансформація, сучасні фічі залишаються.

  1. Типізація та нові фічі
  • TypeScript додає статичну типізацію, enums, interfaces, generics, tuples — чого немає в ES.

  • Нові TS-фічі часто випереджають офіційний стандарт ECMAScript, наприклад ??=, ??, as const.

  1. Приклад сучасного коду TS/ES:
interface User {
  id: number;
  name: string;
}

const users: User[] = [{ id: 1, name: "Alice" }];

const firstUserName = users[0]?.name ?? "Unknown";
console.log(firstUserName);
  • Використано optional chaining ?. і nullish coalescing ?? — обидві ES2020 фічі.
  1. Висновок
  • TypeScript повністю сумісний з останніми стандартами ECMAScript і дає додаткові можливості для безпечної типізації, які не передбачені у JS.
88. Які додаткові можливості TypeScript пропонує поверх ES6+ стандартів?

TypeScript

  1. Статична типізація
  • TypeScript вводить type, interface, enum, tuple, readonly, generics.

  • Дозволяє перевіряти типи на етапі компіляції, чого немає в ES6.

  1. Покращена робота з класами
  • Підтримка модифікаторів доступу: public, private, protected.

  • abstract класи та методи.

  • Підтримка інтерфейсів для контрактів класів.

  1. Розширені конструкції функцій
  • Типізація параметрів і поверненого значення функцій.

  • Optional та default параметри з типовою перевіркою.

  1. Сучасний синтаксис ES6+
  • async/await, destructuring, spread/rest, modules, optional chaining ?., nullish coalescing ??.

  • TS повністю підтримує ці фічі і може транслювати їх під старіші таргети (ES5, ES2015).

  1. Краща інтеграція з IDE та автодоповнення
  • Завдяки типізації, TS забезпечує точне IntelliSense та рефакторинг.
  1. Приклад:
interface User {
  id: number;
  name: string;
}

const users: User[] = [];
const firstUserName = users[0]?.name ?? "Unknown"; // ES2020 фічі + типізація

Основна ідея: TypeScript додає типи, інтерфейси, generics та строгіший контроль, зберігаючи всі сучасні фічі ES6+ і забезпечуючи сумісність із старішими середовищами.

89. Як TypeScript підтримує та працює з асинхронними ітераторами?

TypeScript

  1. Визначення
  • Асинхронний ітератор (AsyncIterator) дозволяє ітерувати над даними, які надходять асинхронно (Promise, стріми, API-виклики).

  • Використовується разом із for await...of.

  1. Типізація в TypeScript
  • Можна явно вказати тип елементів, що повертає асинхронний ітератор:
async function* fetchData(): AsyncGenerator<number> {
  for (let i = 0; i < 3; i++) {
    await new Promise(res => setTimeout(res, 100));
    yield i;
  }
}
  1. Використання for await...of
async function main() {
  for await (const num of fetchData()) {
    console.log(num);
  }
}
main();
  • TypeScript перевіряє, що fetchData() повертає AsyncIterable.
  1. Сумісність з ES2018+
  • Асинхронні ітератори були додані в ES2018.

  • TypeScript підтримує їх, трансформуючи для таргетів ES2018+ без додаткових бібліотек.

  1. Переваги в TS
  • Статична типізація елементів ітератора.

  • Повна інтеграція з Promise та async/await.

  • Можливість створювати потокові дані, не блокуючи основний потік.

90. Які стратегії оптимізації продуктивності застосовують у програмах на TypeScript?

TypeScript

  1. Оптимізація на рівні TypeScript/JavaScript
  • Використовувати типи і інтерфейси для запобігання зайвих перевірок у рантаймі.

  • Мінімізувати використання any, щоб уникнути неочікуваних конверсій.

  • Використовувати readonly, const для зменшення мутабельності.

  1. Асинхронність і конкурентність
  • Promise.all для паралельних запитів.

  • Асинхронні ітератори для потокових даних.

  • Дебаунс і троттл для обробки подій у UI.

  1. Оптимізація фронтенд-коду
  • Динамічний імпорт модулів (import()) → code splitting.

  • Tree-shaking у збірниках (Webpack, Vite).

  • Мінімізація об’єктів і масивів, уникання глибоких копій без потреби.

  1. Профілювання і кешування
  • Використовувати DevTools для виявлення "гарячих" функцій.

  • Кешування результатів складних обчислень (memoization).

  • Кешування API-запитів або локальне збереження даних (IndexedDB, LocalStorage).

  1. Оптимізація Node.js бекенду
  • Використовувати потокову обробку великих даних (streams).

  • Уникати блокуючого коду і синхронних операцій.

  • Підвищити продуктивність через правильний розподіл процесів (cluster, worker_threads).

  1. Приклад memoization у TS
function memoize<T extends (...args: any[]) => any>(fn: T): T {
  const cache = new Map<string, ReturnType<T>>();
  return ((...args: any[]) => {
    const key = JSON.stringify(args);
    if (!cache.has(key)) cache.set(key, fn(...args));
    return cache.get(key)!;
  }) as T;
}

Основна ідея: поєднання типобезпечного коду, асинхронності, кешування та профілювання забезпечує ефективну оптимізацію продуктивності TypeScript-додатків.

91. Як реалізувати та використовувати 'tree shaking' у TypeScript-проєктах?

TypeScript

  1. Визначення Tree Shaking
  • Tree shaking — це процес видалення невикористаного коду під час збірки.

  • Дозволяє зменшити розмір бандла, залишаючи тільки імпортовані функції, класи або константи.

  1. Підтримка у TypeScript
  • TypeScript компілює код у ES-модулі (import / export).

  • Tree shaking працює лише з ESM, не з CommonJS.

  1. Налаштування у TS-проєкті
  • У tsconfig.json:
{
  "compilerOptions": {
    "module": "ESNext",
    "target": "ES6"
  }
}
  • Використовувати збирачі, які підтримують tree shaking: Webpack, Rollup, Vite.
  1. Приклад
// utils.ts
export function used() { console.log("Used"); }
export function unused() { console.log("Unused"); }

// main.ts
import { used } from "./utils";
used();
  • Під час збірки unused() буде видалено, якщо включено tree shaking.
  1. Ключові поради
  • Використовувати іменовані експорти замість export default.

  • Мінімізувати side effects у файлах ("sideEffects": false у package.json).

  • Стежити, щоб всі залежності підтримували ESM.

Основна ідея: TypeScript + ES-модулі дозволяють збирачу видаляти невикористаний код автоматично, зменшуючи розмір бандла і покращуючи продуктивність.

92. Як зменшити розмір фінального бандла в TypeScript-проєкті?

TypeScript

  1. Tree Shaking
  • Використовувати іменовані експорти замість export default.

  • Переконатися, що всі файли ESM-модулі ("module": "ESNext" у tsconfig.json).

  • Вказати "sideEffects": false у package.json.

  1. Code Splitting / Dynamic Imports
  • Розбивати великий код на чанки за допомогою import() для відкладеного завантаження.
const module = await import("./heavyModule");
module.run();
  1. Мінімізація і компресія
  • Використовувати Terser / esbuild / SWC для мінімізації JS-коду.

  • Gzip або Brotli для продакшн-деплойменту.

  1. Видалення неактивного коду та поліморфів
  • Уникати великих утиліт або бібліотек без конкретного імпорту.

  • Імпортувати тільки потрібні функції (import { debounce } from "lodash" замість import _ from "lodash").

  1. Оптимізація типів
  • Типи TS не потрапляють у бандл → використовуй їх для розробки, не додаючи runtime-код.

  • Використовувати import type для імпорту типів, щоб не збільшувати код.

  1. Приклад import type:
import type { User } from "./types";

function greet(user: User) {
  console.log(`Hello, ${user.name}`);
}
  • TS видаляє типи з компільованого JS.

Основна ідея: комбінування tree shaking, динамічних імпортів, мінімізації та правильного використання типів TS дозволяє суттєво зменшити розмір пакета.

93. Які нові можливості з'явилися в останній версії TypeScript?

TypeScript

Основні нові можливості

  1. Покращення продуктивності
  • Оптимізовано структуру коду та алгоритми, що призвело до зменшення часу компіляції та обсягу пам'яті, необхідного для роботи TypeScript .
  1. Покращення роботи з ECMAScript модулями (ESM)
  • Додано нові можливості для кращої підтримки ESM у Node.js та бандлерах .
  1. Покращення роботи з декораторами
  • Реалізація нового стандарту декораторів, що дозволяє краще налаштовувати класи та їхні члени у повторно використовуваному вигляді .
  1. Нова можливість для авторів бібліотек
  • Додано нові способи для авторів бібліотек контролювати виведення типів для узагальнених параметрів .
  1. Розширена підтримка JSDoc
  • Покращено функціональність JSDoc для кращої інтеграції з TypeScript .
  1. Спрощення конфігурації
  • Спрощено налаштування TypeScript для зручнішого використання .

Оптимізації

  • Зменшення розміру пакета: TypeScript 5.0 містить значні зміни в структурі коду, що дозволяє зменшити розмір пакета .

  • Покращення швидкості: Оптимізовано алгоритми, що призвело до покращення швидкості компіляції .

Інші покращення

  • Покращення повідомлень про помилки: TypeScript 5.0 зосереджено на покращенні досвіду розробника через більш детальні та корисні повідомлення про помилки .
94. Як застосовувати шаблонні літеральні типи (template literal types) у TypeScript?

TypeScript

  1. Визначення
  • Шаблонні літеральні типи дозволяють комбінувати рядкові літерали та типи для створення нових типів.

  • Синтаксис схожий на template strings у JavaScript, але працює на рівні типів.

  1. Приклад базового використання
type Color = "red" | "green" | "blue";
type Shade = "light" | "dark";

type ColoredShade = `${Shade}-${Color}`;
// Результат: "light-red" | "light-green" | "light-blue" | "dark-red" | "dark-green" | "dark-blue"
  1. Використання з функціями
function setTheme(theme: ColoredShade) {
  console.log(`Theme set to ${theme}`);
}

setTheme("light-red"); // ✅ коректно
setTheme("dark-yellow"); // ❌ помилка компіляції
  1. Комбінація з умовними типами
type Prefix = "on" | "off";
type Event = "Click" | "Hover";

type EventHandler = `${Prefix}${Event}`;
// "onClick" | "onHover" | "offClick" | "offHover"
  1. Переваги
  • Створення типобезпечних рядкових констант без дублювання.

  • Легко підтримувати конвенції іменування, напр. для CSS-класів або API-параметрів.

  • Інтегрується з union типами та генеріками.

Основна ідея: шаблонні літеральні типи дають змогу створювати складні рядкові типи на основі простих, забезпечуючи сувору типізацію і зменшуючи помилки.

95. Які покращення TypeScript внесла у вивід типів (type inference) для умовних типів?

TypeScript

  1. Визначення
  • Умовні типи (T extends U ? X : Y) дозволяють повертати різні типи залежно від вхідного.

  • TypeScript тепер краще виводить типи при складних комбінаціях, особливо з generic та union типами.

  1. Покращений інференс для union типів
type ElementType<T> = T extends (infer U)[] ? U : T;

type A = ElementType<string[]>; // string
type B = ElementType<number>;   // number
type C = ElementType<(string | number)[]>; // string | number
  • Раніше TypeScript міг не коректно виводити C як string | number, зараз робить точний розбір union.
  1. Умовні типи з infer
  • Покращено вивід типів, коли використовуються вкладені infer для витягування внутрішніх типів.
type ReturnType<T> = T extends (...args: any) => infer R ? R : never;

const fn = (x: number) => x.toString();
type R = ReturnType<typeof fn>; // string
  1. Контекстна типізація для generics
  • TypeScript краще передбачає типи при передачі generic параметрів у функції.
function wrap<T>(value: T) {
  return { value };
}

const wrapped = wrap(42); // { value: number } без явного вказання <number>
  1. Переваги
  • Менше явних анотацій типів.

  • Покращене автодоповнення і попередження помилок.

  • Складні типи (union, intersection, mapped types) стають більш передбачуваними.

Основна ідея: TypeScript зараз точніше виводить типи для умовних конструкцій, generics та infer, зменшуючи потребу у ручних анотаціях і роблячи код більш безпечним.

96. Для чого потрібні позначені елементи (labeled tuples) у TypeScript?

TypeScript

  1. Визначення

Позначені кортежі — це кортежі, де кожен елемент має ім’я, що покращує читаність і автодоповнення.

Синтаксис:

type Point = [x: number, y: number];
const p: Point = [10, 20];
  1. Переваги

Зрозуміліший код: зрозуміло, що означає кожен елемент.

Автодоповнення в IDE: TypeScript підказує назви елементів при деструктуризації.

Типобезпечні операції: помилки при неправильному порядку або типі елемента виявляються на етапі компіляції.

Приклад використання

type Range = [start: number, end: number];

function getRange(): Range {
  return [0, 10];
}

const [start, end] = getRange(); // start та end підказуються IDE
console.log(start, end); // 0 10

Особливості

Позначення є для читабельності, не впливає на runtime.

Працює з readonly кортежами:

const coords: readonly [x: number, y: number] = [1, 2];

Основна ідея: позначені кортежі підвищують зручність та безпеку коду, покращують автодоповнення і допомагають уникати помилок при роботі з позиційними елементами.

97. Як розробник може внести свій внесок у спільноту TypeScript або у сам проект TypeScript?

TypeScript

  1. Внесок у код TypeScript
  • TypeScript — відкритий проект на GitHub: https://github.com/microsoft/TypeScript

  • Можна:

    • фіксити баги (bug reports),

    • додавати або покращувати типи,

    • пропонувати нові фічі (feature requests),

    • робити PR з виправленнями.

  1. Покращення екосистеми
  • Створювати бібліотеки або декларації типів (DefinitelyTyped).

  • Писати npm-пакети з TypeScript і надавати .d.ts файли.

  • Вносити зміни у типи для популярних бібліотек (PR до DefinitelyTyped ).

  1. Документація та навчальні матеріали
  • Дописувати статті, гіди, блог-пости, туторіали.

  • Перекладати офіційну документацію TS.

  • Писати приклади та шаблони проектів для спільноти.

  1. Спільнота та підтримка
  • Відповідати на питання на StackOverflow, Discord, GitHub Discussions.

  • Організовувати або брати участь у локальних/онлайн митапах.

  • Робити огляди бібліотек, нових фіч TypeScript.

  1. Підтримка інструментів
  • Розробляти плагіни для IDE (VSCode), лінтерів, CLI-інструментів.

  • Тестувати нові версії TypeScript у власних проєктах і звітувати баги.

Основна ідея: внесок може бути як кодовий (PR, DefinitelyTyped), так і інформаційний (документація, навчання, спільнота), і будь-який такий внесок робить екосистему TypeScript сильнішою.

98. Яку роль відіграє TypeScript у проєктах з відкритим кодом?

TypeScript

  1. Покращена безпека коду
  • Статична типізація дозволяє уникати багатьох помилок ще на етапі компіляції.

  • У великих відкритих проєктах це критично для підтримки стабільності.

  1. Зрозумілі API та документація через типи
  • Типи слугують самодокументованим кодом.

  • У відкритих бібліотеках користувачі одразу бачать, які параметри і значення очікуються.

  1. Масштабованість
  • TypeScript полегшує роботу з великими кодовими базами та численними контриб’юторами.

  • Генеровані .d.ts файли дозволяють іншим мовам/середовищам (наприклад, JS) безпечно використовувати бібліотеку.

  1. Сумісність з існуючим JS-кодом
  • TypeScript дозволяє поступово типізувати проєкти, що вже існують у JS, без переписування всього коду.
  1. Покращене співробітництво
  • Контриб’ютори легше розуміють структуру і контракти коду.

  • Зменшується кількість помилок у pull request.

  1. Приклади успішних open-source проєктів на TS
  • React, Vue 3, Angular, Deno, NestJS, RxJS — великі проєкти на TypeScript.

  • TypeScript допомагає підтримувати високу якість коду та стабільність API.

Основна ідея: TypeScript у відкритому коді підвищує безпеку, читаємість, масштабованість і полегшує співпрацю багатьох розробників, що критично для open-source екосистеми.

99. Як відстежувати зміни та дотримуватися найкращих практик у TypeScript?

TypeScript

  1. Офіційні джерела
  1. Соціальні мережі та спільнота
  • Twitter акаунти розробників TS (наприклад, @dan_abramov, @andrewbranch).

  • GitHub Discussions, StackOverflow, Reddit (r/typescript).

  1. Підписка на оновлення
  • RSS/Email для TypeScript Blog.

  • YouTube-канали з туторіалами та оглядами нових фіч.

  1. Відкритий код та аналіз бібліотек
  • Стежити за популярними проєктами на TypeScript (React, NestJS, RxJS) для прикладів реального використання.

  • Аналізувати PR і release notes бібліотек, щоб бачити, як використовують TS у продакшн.

  1. Код-рев’ю та стиль коду
  • Використовувати ESLint, TSLint (deprecated, але TS ESLint плагін активний).

  • Дотримуватися офіційних рекомендацій TypeScript щодо strict mode, типів, generics і модулів.

  1. Практика та реальні проєкти
  • Постійно писати код на TypeScript, експериментувати з новими фічами (template literal types, conditional types, decorators).

  • Робити рефакторинг старого JS-коду у TS для закріплення знань.

Основна ідея: комбінувати офіційні ресурси, спільноту, практичний досвід і регулярний аналіз open-source проєктів, щоб залишатися в курсі змін і найкращих практик TypeScript.

100. Як розробнику JavaScript ефективно перейти на TypeScript?

TypeScript

  1. Розуміння основ TypeScript
  • Вивчити базові типи (string, number, boolean, any, unknown, void, null, undefined).

  • Розібратися з інтерфейсами (interface), типами (type) та кортежами (tuple).

  • Ознайомитися з generics і умовними типами.

  1. Налаштування проєкту
  • Ініціалізувати TypeScript: tsc --init.

  • Встановити TypeScript і типи для Node/React/Express:

npm install --save-dev typescript @types/node
  • Налаштувати tsconfig.json (наприклад, "strict": true, "module": "ESNext", "target": "ES6").
  1. Поступовий перехід
  • Почати з переіменування .js файлів у .ts або .tsx (для React).

  • Використовувати allowJs: true у tsconfig для сумісності з існуючим JS.

  • Додавати типи поетапно (any для швидкого старту, потім уточнювати).

  1. Інтеграція з інструментами
  • ESLint з плагіном для TypeScript (@typescript-eslint).

  • IDE з підтримкою TypeScript (VSCode) для автодоповнення та рефакторингу.

  1. Робота з бібліотеками
  • Встановлювати типи для сторонніх пакетів:
npm install --save-dev @types/express
  • Використовувати декларації типів, якщо бібліотека написана на JavaScript.
  1. Практика і рефакторинг
  • Писати невеликі модулі на TypeScript.

  • Постійно рефакторити старий JS-код, додаючи типи і generics.

  • Вчитися на open-source проєктах, написаних на TypeScript.

  1. Кращі практики
  • Використовувати строгий режим (strict: true).

  • Використовувати readonly для незмінних структур.

  • Віддавати перевагу типам над any.

  • Використовувати інтерфейси для API контрактів і DTO.

Основна ідея: почати з базових типів, налаштувати проєкт, поступово типізувати існуючий JS-код і застосовувати кращі практики для підтримки стабільності та безпеки коду.

101. ???

TypeScript

  • Coming soon...😎

About

Найпопулярніші запитання та відповіді на співбесіді з TypeScript

Topics

Resources

License

Code of conduct

Contributing

Security policy

Stars

Watchers

Forks

Releases

No releases published

Sponsor this project

  •  

Packages

No packages published