1. Поясніть, що таке TypeScript та які його ключові відмінності від JavaScript.
TypeScript — це надбудова над JavaScript, яка додає статичну типізацію, інтерфейси та інші можливості для підвищення надійності коду.
-
Типізація: TS має статичні типи, JS — динамічні.
-
Розробка: TS виявляє помилки на етапі компіляції, JS — під час виконання.
-
Сумісність: TS компілюється у JS, тому працює у всіх середовищах JS.
-
Інструменти: краща підтримка IDE (автодоповнення, рефакторинг).
2. Що означає твердження, що TypeScript є надмножиною JavaScript?
- Це означає, що будь-який коректний JavaScript-код є також коректним TypeScript-кодом. TypeScript розширює можливості JS, додаючи типи та інші фічі, але при цьому не змінює базову мову.
3. Які основні вбудовані типи даних підтримує TypeScript?
Основні типи в TypeScript:
-
string
— рядки -
number
— числа (цілі та з плаваючою крапкою) -
boolean
— логічні значення -
null
таundefined
-
any
— будь-який тип -
unknown
— невідомий тип (безпечніша альтернатива any) -
void
— відсутність значення (часто у функціях) -
never
— функція ніколи не повертає значення (наприклад, кидає помилку) -
object
— об’єкти -
Масиви
(type[] або Array) -
Кортежі
([type1, type2, ...]) -
enum
— перерахування
4. Які способи оголошення змінних у 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 описують структуру об’єкта (його властивості та їх типи), не створюючи конкретної реалізації. Вони допомагають забезпечити контракт між частинами коду.
Основні можливості:
- Опис форми об’єкта:
interface User {
id: number;
name: string;
isAdmin?: boolean; // необов’язкове поле
}
const user: User = { id: 1, name: "Alice" };
-
Підтримка опціональних властивостей (?).
-
Можливість розширення (extends).
-
Використання для опису структур функцій, класів та масивів.
По суті, інтерфейси — це спосіб зробити код більш передбачуваним і безпечним.
6. Що таке enum у 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 із врахуванням типів?
- Функції визначаються так само, як у 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 автоматично визначає тип змінної чи результату функції на основі наданого значення без явного оголошення.
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 і як правильно їх використовувати?
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 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 — це надбудова над JS-класами. Вони працюють так само, як у JS, але доповнені системою типів:
-
можна оголошувати типи для полів, параметрів і повертаних значень;
-
є модифікатори доступу (public, private, protected, readonly);
-
є abstract класи та методи;
-
підтримка implements для інтерфейсів;
-
підтримка generics.
У рантаймі вони компілюються в звичайні JS-класи, а типи прибираються.
12. Як правильно реалізувати спадкування класів у 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 має 4 модифікатори доступу:
-
public
(за замовчуванням) – доступний скрізь. -
private
– доступний тільки всередині цього класу. -
protected
– доступний у класі та його нащадках. -
readonly
– властивість доступна тільки для читання після ініціалізації.
Вони впливають лише на етапі компіляції (для контролю типів), у рантаймі JavaScript цього обмеження немає.
14. Що таке абстрактні класи в 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 і які особливості їх використання?
Конструктор (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 для властивостей класу і як їх застосовувати?
Декоратори — це функції, які дозволяють змінювати або розширювати поведінку класів, методів, властивостей або параметрів. Декоратор властивості отримує ціль (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?
Геттери та сеттери дозволяють контролювати доступ до властивостей класу.
-
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 дозволяє перевантажувати методи через сигнатури, але тільки одна реалізація. Це означає: можна оголосити кілька варіантів виклику методу з різними параметрами, а в тілі методу реалізувати логіку з перевіркою типів/кількості аргументів.
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?
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)?
Псевдонім типу (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 і як їх застосовувати?
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 і як вони працюють?
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 і в яких випадках їх варто застосовувати?
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 і для чого вони потрібні?
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
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 дозволяє виводити типи з існуючих значень за допомогою typeof і keyof.
- Отримання типу з об’єкта
const user = {
id: 1,
name: "Alice",
isAdmin: true
};
type User = typeof user;
// User = { id: number; name: string; isAdmin: boolean }
- Отримання типів ключів
type UserKeys = keyof typeof user;
// "id" | "name" | "isAdmin"
- Комбінація з літеральними типами
const roles = ["admin", "user", "guest"] as const;
type Role = typeof roles[number];
// "admin" | "user" | "guest"
Це дозволяє уникати дублювання коду й гарантує синхронізацію типів з даними.
27. Що таке узагальнені типи (Generics) у TypeScript і для чого вони потрібні?
Generics — це параметризовані типи, які дозволяють писати універсальний і багаторазовий код, зберігаючи типобезпеку. Вони дозволяють відкладати визначення конкретного типу до моменту використання.
function identity(value: any): any {
return value;
}
- Проблема: втрачається тип.
function identity<T>(value: T): T {
return value;
}
let num = identity<number>(42); // num: number
let str = identity("Hello"); // str: string (TS вивів тип автоматично)
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?
Узагальнена функція визначається через параметр типу в кутових дужках <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]
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 і для чого вони використовуються?
Узагальнені інтерфейси дозволяють описати контракт, який працює з різними
типами, зберігаючи типобезпеку. Для цього в інтерфейс додають параметри типів
<T>
(або кілька).
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 можна робити класи параметризованими типами, додаючи параметр <T>
після імені класу. Це дозволяє створювати універсальні класи, які працюють з
різними типами даних, зберігаючи типобезпеку.
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);
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 можна обмежити 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 і як він працює?
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 і як його оголосити/використати?
Readonly<T>
— це вбудований утилітний тип, який робить усі властивості об’єкта
тільки для читання (неможливо змінювати після ініціалізації).
type User = {
id: number;
name: string;
};
const u: Readonly<User> = {
id: 1,
name: "Alice"
};
u.name = "Bob"; // ❌ Помилка: властивість доступна тільки для читання
type Readonly<T> = {
readonly [P in keyof T]: T[P];
};
- Тобто це mapped type, який додає модифікатор readonly до кожної властивості.
Використовується для іммутабельних даних, DTO та запобігання випадковим змінам.
34. Що таке mapped types у 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];
};
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 і як вони працюють?
Умовні типи дозволяють описувати залежності між типами за допомогою конструкції 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?
keyof
-
keyof створює об’єднання (union) ключів заданого типу.
-
Використовується для обмеження значень ключами інтерфейсу/типу.
type User = { id: number; name: string; active: boolean };
type UserKeys = keyof User;
// "id" | "name" | "active"
- Дозволяють отримати тип значення за конкретним ключем.
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?
- Твердження типів (Type Assertion)
-
Це інструкція для компілятора, що значення має певний тип.
-
Не змінює значення у рантаймі.
-
Використовується, коли розробник краще знає тип, ніж TypeScript.
let value: unknown = "Hello";
let strLength = (value as string).length; // "повір, це string"
- Приведення типів (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 і для чого вони потрібні?
Partial<T>
- Робить усі властивості опціональними.
type User = { id: number; name: string; };
type PartialUser = Partial<User>;
// { id?: number; name?: string }
- Використовується для об’єктів оновлення/патчів.
Required<T>
- Робить усі властивості обов’язковими (знімає ?).
type UserOptional = { id?: number; name?: string; };
type RequiredUser = Required<UserOptional>;
// { id: number; name: string }
- Корисно для валидації, коли потрібен повний об’єкт.
Readonly<T>
- Робить усі властивості доступними тільки для читання.
type User = { id: number; name: string; };
type ReadonlyUser = Readonly<User>;
const u: ReadonlyUser = { id: 1, name: "Alice" };
u.name = "Bob"; // ❌ Помилка
- Застосовується для іммутабельних даних.
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 і в яких випадках він застосовується?
never
-
Це спеціальний тип, який означає значення, що ніколи не існує.
-
Використовується там, де функція або вираз не повертає значення взагалі.
- Функція, яка ніколи не завершується успішно
function fail(message: string): never {
throw new Error(message);
}
- Функція з нескінченним циклом
function infiniteLoop(): never {
while (true) {}
}
- 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
-
Кожен файл з 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
-
Це спосіб групувати логіку всередині одного глобального об’єкта.
-
Використовувалися до появи модулів для уникнення колізій у глобальному просторі імен.
namespace Utils {
export function add(a: number, b: number): number {
return a + b;
}
export const PI = 3.14;
}
console.log(Utils.add(2, 3));
-
У legacy-проєктах або коли немає системи модулів (наприклад, код вбудовується напряму в
<script>
безbundler
). -
Для простих демо/маленьких проєктів, де немає потреби в модульній структурі.
-
Для декларацій
.d.ts
файлів, щоб групувати типи/інтерфейси.
-
У сучасному TypeScript стандартом є ES6 модулі (import/export).
-
Bundlers (Webpack, Vite, esbuild) та Node.js працюють із модулями, а не namespace.
-
Простори імен у великих проєктах ускладнюють масштабування.
-
Нові проєкти → використовувати модулі.
-
Простори імен → тільки у специфічних випадках (legacy, declaration merging, глобальні бібліотеки без модулів).
42. У чому різниця між внутрішніми та зовнішніми модулями в TypeScript?
- Внутрішні модулі (старий підхід)
-
Використовували ключове слово namespace.
-
Код групується в один глобальний об’єкт.
-
Завантаження відбувається через
<script>
без системи модулів.
namespace Utils {
export function add(a: number, b: number) {
return a + b;
}
}
console.log(Utils.add(2, 3));
Зараз вважаються застарілими — замінені на ES6-модулі.
- Зовнішні модулі (сучасний підхід)
-
Використовують 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 експортувати та імпортувати модулі?
- Іменований експорт
// 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);
- Можна імпортувати тільки потрібне.
- Експорт за замовчуванням (default)
// logger.ts
export default function log(message: string) {
console.log("LOG:", message);
}
// app.ts
import log from "./logger";
log("hello");
- Імпортується без {}, ім’я можна змінювати довільно.
- Перейменування при імпорті/експорті
// math.ts
export { add as sum };
// app.ts
import { sum as addNumbers } from "./math";
- Імпорт у вигляді простору імен
// app.ts
import * as MathUtils from "./math";
console.log(MathUtils.add(2, 3));
- Повторний експорт (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 знаходить файл, що відповідає шляху з import або require. Приклад:
import { add } from "./math";
TypeScript має зрозуміти, чи це math.ts, math.d.ts, math.js чи інший файл.
- Classic (старий режим, до ES6)
-
Працює подібно до компілятора C/C++.
-
Використовується для старих скриптів, без node_modules.
-
Пошук іде відносно файлу, де зроблений імпорт.
Використовується рідко, в legacy-коді.
- 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 повністю базується на 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.
TypeScript дозволяє імпортувати CommonJS-модулі:
import fs from "fs"; // default-імпорт
import * as path from "path"; // namespace-імпорт
Працює завдяки прапору "esModuleInterop": true у tsconfig.json.
- TypeScript → ES6
-
TS просто додає типи, які зникають при компіляції.
-
Залишається чистий ES6-код.
- Interop із CommonJS
- Можна імпортувати старі бібліотеки (require) без проблем.
- Default vs Named exports
-
ES6 → default експортується як export default.
-
TS дозволяє змішувати default та named (через esModuleInterop).
-
TypeScript сумісний із ES6-модулями 1:1.
-
Додатково підтримує CommonJS через компілятор.
-
Використання "module": "ESNext" і "esModuleInterop": true робить код максимально універсальним.
46. Що таке декоратори у TypeScript і як їх використовують?
Декоратори — це спеціальні функції, які можна застосовувати до класів, методів, властивостей або параметрів, щоб змінювати або розширювати їхню поведінку. Вони працюють як метадані + синтаксичний цукор над патерном higher-order functions.
- У TypeScript декоратори — експериментальна функція, вмикаються прапором:
{
"experimentalDecorators": true,
"emitDecoratorMetadata": true
}
function MyDecorator(target: any) {
console.log("Декоратор застосовано до:", target);
}
@MyDecorator
class Example {}
- Класів
function LogClass(constructor: Function) {
console.log("Class:", constructor.name);
}
@LogClass
class User {}
- Методів
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);
- Властивостей
function Readonly(target: any, propertyKey: string) {
Object.defineProperty(target, propertyKey, { writable: false });
}
class Car {
@Readonly
brand: string = "Tesla";
}
- Параметрів
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 і як вони змінюють поведінку класів?
Декоратор класу — це функція, яка отримує конструктор класу як аргумент. Він може:
-
додати метадані,
-
змінити або підмінити конструктор,
-
модифікувати/доповнити прототип.
type ClassDecorator = <TFunction extends Function>(target: TFunction) => TFunction | void;
function LogClass(constructor: Function) {
console.log(`Клас створено: ${constructor.name}`);
}
@LogClass
class User {}
- При завантаженні модуля виведе: Клас створено: User.
function WithTimestamp(constructor: Function) {
constructor.prototype.timestamp = new Date();
}
@WithTimestamp
class Order {}
const o = new Order();
console.log(o.timestamp); // Дата створення
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 і як їх використовувати?
Декоратор методу — це функція, яка застосовується до методу класу. Він отримує:
-
target
— прототип класу (для екземплярного методу) або конструктор (для статичного). -
propertyKey
— ім’я методу. -
descriptor
— PropertyDescriptor, що описує метод (можна змінювати).
Використовується для перехоплення викликів, логування, кешування, валідації тощо.
type MethodDecorator = (
target: Object,
propertyKey: string | symbol,
descriptor: PropertyDescriptor
) => void | PropertyDescriptor;
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]"
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 вже викликано!"
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 і як вони працюють?
Декоратори аксесорів застосовуються до геттерів або сеттерів у класах. Вони працюють майже так само, як декоратори методів, але застосовуються до get/set.
- Сигнатура:
type AccessorDecorator = (
target: Object,
propertyKey: string | symbol,
descriptor: PropertyDescriptor
) => void | PropertyDescriptor;
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
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 і як їх використовувати?
Декоратор властивості застосовується до поля класу. На відміну від методів чи аксесорів, він не має доступу до PropertyDescriptor, оскільки властивості ще не існують на момент компіляції.
- Сигнатура:
type PropertyDecorator = (
target: Object,
propertyKey: string | symbol
) => void;
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
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 є обов’язковим
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?
Декоратори — це спеціальні анотації для класів, методів, властивостей чи параметрів, які дозволяють додавати поведінку або метадані, не змінюючи безпосередньо бізнес-логіку. Вони реалізують принципи AOP (Aspect-Oriented Programming) — винесення повторюваних завдань (логування, валідація, DI) в окремі аспекти.
-
Виносять повторювану логіку (логування, кешування, валідацію) у незалежні функції.
-
Роблять код декларативним — замість "писати вручну" можна описати поведінку через анотацію.
-
Додають метадані до класів/методів/властивостей, які можна зчитувати у runtime.
-
Сприяють модульності — декоратор можна підключити/відключити без зміни основного коду.
Без декоратора:
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" };
}
}
- Логіка логування винесена окремо, метод залишився чистим.
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 і як вона використовується разом із декораторами?
Рефлексія — це можливість коду отримувати метадані про себе під час
виконання (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
записує метадані, а валідаційна логіка може потім їх використовувати.
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?
tsconfig.json
— це конфігураційний файл TypeScript, який:
-
описує як компілювати проєкт (шлях до вихідних файлів, вихідна директорія, таргетовану версію JS тощо);
-
визначає налаштування компілятора (строгість типів, модульну систему, JSX);
-
дозволяє інструментам (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 вказати, які файли включати або виключати під час компіляції?
-
include
— список файлів/папок, які слід компілювати. -
exclude
— список файлів/папок, які потрібно ігнорувати. -
files
— явний перелік файлів (рідко використовується).
- Включення всіх файлів із src
{
"include": ["src"]
}
- Включення тільки .ts і .tsx файлів
{
"include": ["src/**/*.ts", "src/**/*.tsx"]
}
- Виключення тестів і node_modules
{
"include": ["src"],
"exclude": ["node_modules", "**/*.test.ts"]
}
- Використання files для точного списку
{
"files": ["src/index.ts", "src/app.ts"]
}
- У такому разі компілюватимуться тільки ці файли, навіть якщо є інші.
-
Якщо є
files
→ беруться тільки вони. -
Якщо є
include
→ компілятор бере ці файли + всі залежності. -
exclude
завжди має вищий пріоритет і "вирізає" файли зinclude
.
55. Які найчастіше використовувані параметри в compilerOptions файлі tsconfig.json?
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 і для чого вони потрібні?
Source maps — це файли, які дозволяють браузеру або інструментам налагодження зіставляти зкомпільований JavaScript із оригінальним TypeScript-кодом.
-
Формат: .js.map
-
Дозволяє дебагати TS прямо в браузері або IDE, бачачи рядки та колонки TS, а не JS.
{
"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 і як вона працює?
Поступова збірка (Incremental build) — це режим компілятора TypeScript, який дозволяє компілювати тільки ті файли, які змінилися, замість повної перекомпіляції всього проєкту.
-
Зменшує час збірки великих проєктів.
-
Зберігає інформацію про попередню збірку у файлі .tsbuildinfo.
{
"compilerOptions": {
"incremental": true,
"outDir": "./dist"
}
}
-
При першій компіляції створюється tsconfig.tsbuildinfo.
-
При наступних компіляціях TS перевіряє, які файли змінилися, і компілює лише їх.
-
Проєкт з 1000 файлів.
-
Змінено тільки 2 файли.
-
При 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 і як він працює?
noImplicitAny
— це параметр компілятора, який забороняє TypeScript автоматично
підставляти тип any там, де він не вказаний явно.
-
Якщо компілятор не може вивести тип і немає явного типу, він видає помилку.
-
Це допомагає робити код більш типізованим і безпечним.
{
"compilerOptions": {
"noImplicitAny": true
}
}
function add(a, b) {
return a + b;
}
-
a
таb
автоматично отримують типany
. -
TypeScript не видає помилку, але типізація відсутня.
function add(a, b) {
return a + b;
}
// ❌ Помилка: Parameter 'a' implicitly has an 'any' type
- Тепер потрібно явно вказати типи:
function add(a: number, b: number): number {
return a + b;
}
[1, 2, 3].map((x) => x * 2); // Без помилки, тип виведено
- Тут TypeScript може вивести тип (number), тому помилка не з’являється.
-
noImplicitAny: true = сувора політика типів.
-
Запобігає неявному any, роблячи код більш безпечним.
-
Рекомендується завжди увімкати у проєктах, особливо для великих команд.
59. Як увімкнути суворі перевірки на null та undefined у TypeScript?
Сувора перевірка на null
і undefined
допомагає уникати помилок типу
Cannot read property of undefined
.
-
Параметр strictNullChecks змушує TypeScript розрізняти типи null та undefined від інших типів.
-
Без нього всі типи за замовчуванням можуть бути null/undefined.
{
"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) для сторонніх бібліотек?
- Бібліотека має власні типи
- Деякі пакети вже містять
.d.ts
у собі (наприклад,axios
,react
).
import axios from "axios";
axios.get<string>("https://api.test"); // працює, бо типи вбудовані
- Використання DefinitelyTyped
- Якщо типів немає в пакеті → інсталюємо окремо:
npm install --save-dev @types/lodash
- Після цього TS автоматично підхоплює типи.
- Створення власних визначень
Якщо немає офіційних або community-типів:
- Створюємо файл
*.d.ts
, наприкладcustom.d.ts
:
declare module "legacy-lib" {
export function doSomething(input: string): number;
}
- Тепер можна імпортувати:
import { doSomething } from "legacy-lib";
- Тип any як fallback
Якщо типи зовсім невідомі:
declare module "unknown-lib";
-
Усі імпорти з цього модуля будуть типу any.
-
Це останній варіант, коли немає часу писати типи.
- Конфігурація пошуку типів
У tsconfig.json
можна вказати, де брати типи:
{
"compilerOptions": {
"typeRoots": ["./types", "./node_modules/@types"]
}
}
typeRoots
→ де шукати визначення.
types
→ обмежити список типів, які підключаються.
-
Використовуємо вбудовані типи пакета.
-
Якщо їх немає → ставимо
@types/…
. -
Якщо і там немає → пишемо власні
.d.ts
. -
Як fallback →
any
.
61. Що таке DefinitelyTyped і яке його відношення до TypeScript?
DefinitelyTyped — це великий open-source репозиторій GitHub, у якому
зберігаються файли визначень типів (.d.ts
) для сторонніх JavaScript-бібліотек,
які самі по собі не містять типів.
-
Публікується у вигляді npm-пакетів у просторі імен
@types
.
-
Хтось пише бібліотеку на JS без TypeScript.
-
Спільнота додає до DefinitelyTyped файл
index.d.ts
, який описує API цієї бібліотеки. -
Ви встановлюєте типи через npm:
npm install --save-dev @types/lodash
- TypeScript автоматично знаходить типи й дозволяє безпечно працювати з бібліотекою:
import _ from "lodash";
const nums: number[] = [1, 2, 3];
const doubled = _.map(nums, x => x * 2); // ✅ коректні типи
-
TypeScript не постачається з типами для всіх JS-бібліотек.
-
DefinitelyTyped заповнює цю прогалину → робить будь-яку популярну JS-бібліотеку "типобезпечною".
-
Визначення типів автоматично інтегруються з TS без додаткових налаштувань.
-
DefinitelyTyped
= репозиторій типів для JS-бібліотек. -
Публікується як пакети
@types/*
. -
Дозволяє використовувати бібліотеки без вбудованих типів у TypeScript із повноцінною підтримкою IntelliSense і перевіркою типів.
62. Як у TypeScript використовувати файли типів (@types) з npm?
- Якщо бібліотека має вбудовані типи
Багато сучасних пакетів (наприклад, axios, react) уже містять index.d.ts у собі. Додатково нічого ставити не треба:
import axios from "axios";
axios.get<string>("https://api.test");
- Якщо бібліотека не має типів
Тоді шукаємо їх у npm-namespace @types:
npm install --save-dev @types/lodash
Тепер у коді:
import _ from "lodash";
const result = _.chunk([1,2,3,4], 2); // тип: number[][]
- Якщо немає готових типів у @types
Створюємо власний файл декларацій, наприклад src/types/custom-lib.d.ts:
declare module "custom-lib" {
export function doSomething(x: string): number;
}
TypeScript автоматично підхопить ці типи (якщо шлях входить у typeRoots або в include у tsconfig.json).
- Керування типами через tsconfig.json
За замовчуванням TS підтягує всі типи з node_modules/@types.
Можна обмежити:
{
"compilerOptions": {
"types": ["node", "jest"]
}
}
→ тоді інші типи ігноруються.
-
Вбудовані типи → просто імпортуємо.
-
Через @types → ставимо npm install @types/....
-
Власні типи → створюємо .d.ts.
-
Контроль підключення → через tsconfig.json.
63. Як інтегрувати TypeScript у проєкт Angular?
- Angular побудований поверх TypeScript
-
Починаючи з Angular 2+, офіційний фреймворк завжди працює з TS як основною мовою.
-
Angular CLI автоматично генерує tsconfig.json з оптимальними параметрами.
- Створення проєкту
npm install -g @angular/cli
ng new my-app
→ CLI налаштує TypeScript, компіляцію та структуру файлів.
- Конфігурація TypeScript
У корені буде tsconfig.json
, наприклад:
{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"strict": true,
"experimentalDecorators": true,
"emitDecoratorMetadata": true
}
}
experimentalDecorators
+emitDecoratorMetadata
→ потрібні для декораторів Angular (@Component
,@Injectable
тощо).
- Використання 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’и для моделей даних.
- Типізація сервісів і 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
- Використати Next.js:
npx create-next-app@latest
- Або Vite:
npm create vite@latest my-app -- --template react-ts
- Конфігурація
tsconfig.json
Приклад базових налаштувань:
{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"jsx": "react-jsx",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true
}
}
- Типізація компонентів
- Функціональний компонент:
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>;
- Хуки з дженериками
const [items, setItems] = useState<string[]>([]);
const ref = useRef<HTMLInputElement>(null);
- Типізація подій
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
console.log(e.target.value);
};
- Типи для 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-проєкт?
- Створення Vue + TypeScript проєкту
Зараз офіційний спосіб — Vite + Vue CLI (create-vue):
npm create vue@latest
Під час створення проєкту обираєте Add TypeScript? -> Yes.
- Використання
.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>
- Типізація пропсів і емісій
<script setup lang="ts">
interface Props {
msg: string
}
const props = defineProps<Props>()
const emit = defineEmits<{
(e: "update", value: number): void
}>()
</script>
- Типи для композиційного 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 }
}
- Конфігурація
tsconfig.json
Важливі опції:
{
"compilerOptions": {
"strict": true,
"module": "ESNext",
"target": "ESNext",
"moduleResolution": "Node",
"jsx": "preserve",
"allowJs": false,
"types": ["vite/client"]
}
}
- Додаткові пакети
-
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-додатках?
- Ініціалізація проєкту
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
}
}
- Структура проєкту
src/
├─ index.ts
└─ utils.ts
- Приклад коду з типами
// 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"))
- Запуск
Прямо через ts-node (зручно для девелопменту):
npx ts-node src/index.ts
З компіляцією в JS (продакшн):
npx tsc
node dist/index.js
- Використання з 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"))
- Переваги TypeScript у Node.js
-
Безпечні типи для API (Express, FS, DB).
-
Зручні автопідказки в IDE.
-
Краща підтримка великих кодових баз.
-
Легше уникати runtime-помилок.
67. Які переваги використання статичного аналізатора коду (TSLint або ESLint з TypeScript-плагіном)?
Переваги:
-
Єдиний кодстайл — забезпечує консистентність у команді (наприклад, лапки ' vs ").
-
Раннє виявлення помилок — ловить некоректні конструкції ще до запуску (невикористані змінні, неправильні імпорти).
-
Краща читабельність — автоматичне форматування та правила полегшують підтримку коду.
-
Інтеграція з IDE/CI — помилки видно під час розробки, а також можна блокувати комміти/білди з помилками.
-
Безпека — правила допомагають уникати небезпечних практик (наприклад, any або eval).
-
Автоматичні виправлення — більшість проблем виправляється командою eslint --fix.
68. Як налаштувати збірку проєкту на TypeScript за допомогою Webpack?
Крок 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 Compiler (tsc): компілює TS → JS і перевіряє типи.
-
Babel: трансформує TS-синтаксис → JS, але ігнорує типи (типи видаляються на етапі компіляції).
Тобто:
-
Якщо потрібна лише підтримка синтаксису (наприклад, у React), достатньо Babel.
-
Якщо потрібна перевірка типів, треба додатково запускати tsc --noEmit або використовувати fork-ts-checker-webpack-plugin.
- Залежності
npm install --save-dev @babel/core @babel/preset-env @babel/preset-typescript \
@babel/preset-react
.babelrc
{
"presets": [
"@babel/preset-env",
"@babel/preset-react",
"@babel/preset-typescript"
]
}
package.json
scripts
"scripts": {
"build": "babel src --extensions \".ts,.tsx\" --out-dir dist",
"type-check": "tsc --noEmit"
}
-
Проєкт на React з сучасними фічами, які Babel трансформує краще за tsc.
-
Потрібна інтеграція з Webpack/Rollup/Vite.
-
Хочеш швидшу збірку (Babel швидший, але без type-check).
-
Якщо потрібен чистий TS-проєкт без зайвих тулів.
-
Якщо важлива строга перевірка типів у компіляції.
-
Babel робить трансформацію коду.
-
tsc --noEmit або fork-ts-checker-webpack-plugin робить перевірку типів.
70. Як TypeScript інтегрується з Visual Studio Code та іншими IDE?
-
Вбудована підтримка: 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 читає конфігурацію проєкту й підлаштовує підсвічування.
-
Використовують 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-застосунку?
Основні принципи структурування
- Використовуй tsconfig.json для централізованих налаштувань
-
strict: true → строгі перевірки.
-
baseUrl + paths → заміна відносних імпортів (../../../) на зрозумілі alias-и.
- Організація папок
src/
components/ // UI або бізнес-компоненти
services/ // API, робота з даними
models/ // інтерфейси та типи
utils/ // хелпери
hooks/ // кастомні React hooks
config/ // налаштування
index.ts // точка входу
- Кожен модуль експортує лише те, що потрібно (через index.ts у папці).
- Використовуй інтерфейси/типи для контрактів
-
interface для моделей даних та API.
-
type для Union/Intersection.
- Сервіси й утиліти мають бути незалежні від UI
- Розділяй бізнес-логіку (services, models) і UI-логіку (components).
- Використовуй Barrel exports (index.ts)
// src/services/index.ts
export * from './authService';
export * from './userService';
- Розділяй типи в окрему папку types/
-
Добре для спільних моделей (User, Product).
-
Використовуй @types із npm, якщо є.
- Використовуй ESLint + Prettier з правилами для TS
-
Забезпечує єдиний стиль коду.
-
Ловить помилки раніше компіляції.
- Type-safe API-клієнти
-
Використовуй axios/fetch з власними типами респонсів.
-
Винось DTO (Data Transfer Objects) окремо.
- Ніколи не вимикай строгі прапорці
- noImplicitAny, strictNullChecks, noUnusedLocals.
- Використовуй async/await з типами
- Обгортай відповіді від API у Promise.
✅ Висновок:
- Правильна структура TS-проєкту = чітке розділення шарів (UI / бізнес / дані), строга типізація, єдиний стиль коду, мінімізація "any".
72. Як правильно використовувати async/await у 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-застосунках і як їх правильно типізувати?
Локальний стан (React / Vue / Angular)
- Використовуй узагальнені хуки/сервіси з чіткими типами.
const [count, setCount] = useState<number>(0);
Глобальний стан
- Redux Toolkit + TypeScript
-
Типізація RootState, AppDispatch.
-
Використовуй createSlice, щоб уникати boilerplate.
interface CounterState { value: number }
const initialState: CounterState = { value: 0 };
- MobX
-
Класи з observable + інтерфейси.
-
Просте зв’язування з TS через декоратори.
- 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?
- Singleton – один екземпляр класу (наприклад, конфігурація, логгер).
class Logger {
private static instance: Logger;
private constructor() {}
static getInstance() {
if (!Logger.instance) Logger.instance = new Logger();
return Logger.instance;
}
}
- Factory Method / Abstract Factory – створення об’єктів через фабрику з типами.
- 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); }
}
-
Decorator – динамічно додає поведінку до класу.
-
Proxy – контроль доступу або кешування.
- 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)); }
}
-
Strategy – змінюваний алгоритм (наприклад, різні способи валідації).
-
Command – інкапсуляція дії у вигляді об’єкта.
-
Mediator – координація взаємодії між об’єктами.
-
Інтерфейси та дженерики роблять шаблони більш строгими.
-
Decorators (експериментальні) дозволяють реалізовувати Decorator, DI та Aspect-Oriented Programming.
-
Union/Discriminated Unions часто спрощують реалізацію State чи Strategy.
75. Які основні підходи для налагодження TypeScript-застосунків?
- Source Maps
- У
tsconfig.json
додати:
{
"compilerOptions": {
"sourceMap": true
}
}
- Це дозволяє дебагеру (Chrome DevTools, VS Code) показувати оригінальний .ts код замість згенерованого .js.
- 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-файлах.
- Debug у браузері
-
Використовувати
webpack
абоvite
з підтримкою source maps. -
Відкрити Chrome DevTools → вкладка Sources → знаходиш .ts файл → ставиш брейкпоінт.
- Консольні інструменти
console.log
,console.table
,debugger
працюють так само, але з відображенням через source maps.
- Node.js Debugging
- Запуск з Node.js:
node --inspect-brk dist/index.js
- Підключаєшся через Chrome DevTools або VS Code.
- Unit-тести з дебагом
-
Якщо використовуєш Jest / Mocha → можна запускати з
--inspect
. -
Це допомагає відслідковувати помилки ще до рантайму.
✅ Ключ: завжди компілюй з sourceMap
і налагоджуй оригінальний .ts
код, а не
згенерований .js
.
76. Як писати модульні тести для TypeScript-коду?
- Вибір тестового фреймворку
-
Найчастіше: Jest, Mocha + Chai, Vitest.
-
Для фронтенду (React/Angular): зазвичай Jest.
- Налаштування TypeScript
- Встановлення залежностей (на прикладі Jest):
npm install --save-dev jest ts-jest @types/jest
- Ініціалізація:
npx ts-jest config:init
- Приклад функції (яку тестуємо):
// src/utils/math.ts
export function add(a: number, b: number): number {
return a + b;
}
- Приклад тесту
// tests/math.test.ts
import { add } from "../src/utils/math";
test("adds two numbers", () => {
expect(add(2, 3)).toBe(5);
});
- Запуск тестів
npx jest
- Типізація у тестах
-
Використовуєш ті самі TS-типи.
-
Якщо треба mock-и →
jest.mock
абоts-mockito
.
- Кращі практики
-
Тести зберігати в
__tests__/
або*.test.ts
. -
Перевіряти і позитивні, і негативні кейси.
-
Використовувати beforeEach / afterEach для підготовки оточення.
-
Перекривати тільки те, що потрібно (не мокати все без потреби).
✅ Резюме: модульні тести в TypeScript пишуться так само, як у JavaScript, але з додатковими перевагами типізації. Найзручніше — Jest + ts-jest.
77. Які тестові фреймворки зазвичай застосовують для TypeScript-проєктів?
Найпопулярніші
- Jest
-
Найпоширеніший для фронтенду та Node.js.
-
Підтримує TypeScript через ts-jest.
-
Вбудовані мокі, асинхронні тести, snapshot testing.
- Vitest
-
Швидкий, сучасний, інтегрується з Vite.
-
Підтримує TS нативно.
-
API сумісний із Jest.
- Mocha + Chai
-
Класичний стек для Node.js.
-
ts-node дозволяє запускати тести на TS без компіляції.
-
Чудово підходить для серверних застосунків.
- AVA
-
Мінімалістичний, паралельне виконання тестів.
-
Підтримка TS через ts-node/register.
- Jasmine
-
Використовувався для Angular, можна писати тести на TS.
-
Менш популярний сьогодні через Jest/Vitest.
78. Як виконувати наскрізне (E2E) тестування у TypeScript-застосунках?
- Cypress
-
Дуже популярний для фронтенду.
-
Підтримує TypeScript через tsconfig.json.
-
Вбудований девтулс, мок-сервер, зручні API для селекторів.
- Playwright
-
Кросбраузерне тестування (Chromium, Firefox, WebKit).
-
TypeScript підтримується "з коробки".
-
Можна тестувати UI, API, мобільні емуляції.
- Puppeteer
-
Автоматизація браузера Chrome.
-
TypeScript через npm типи (@types/puppeteer).
- tsconfig.json
{
"compilerOptions": {
"target": "ES6",
"module": "CommonJS",
"strict": true,
"esModuleInterop": true,
"types": ["cypress", "node"]
},
"include": ["cypress/**/*.ts"]
}
- 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');
});
});
- 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/);
});
-
Типізувати тести і селектори через інтерфейси або константи.
-
Використовувати Page Object Model (POM) для організації коду тестів.
-
Писати окремі тести для функціональних сценаріїв, а не перевіряти все одним тестом.
-
Інтегрувати E2E у CI/CD (GitHub Actions, GitLab CI) з відловом помилок.
✅ Висновок: TypeScript + E2E тести дозволяють мати типобезпечні, надійні та підтримувані тести для UI і API.
-
Cypress → швидкий для фронтенду.
-
Playwright → кросбраузерна автоматизація.
79. Як налаштувати і використовувати TypeScript з Express.js?
- Ініціалізуєш проект:
npm init -y
npm install express
npm install -D typescript ts-node @types/node @types/express
-
Створюєш tsconfig.json (базово з npx tsc --init).
-
Пишеш код у 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"));
- Запускаєш у дев-режимі:
npx ts-node src/index.ts
- Для продакшена — компілюєш (npx tsc) і запускаєш node dist/index.js.
Цього мінімуму достатньо, щоб підняти Express-сервер на TypeScript.
80. Як створювати RESTful сервіси з використанням TypeScript?
- Архітектура
-
Використовують Express/Koa/NestJS як HTTP-фреймворк.
-
Розділяють шари: routes → controllers → services → repositories.
-
Використовують DTO/інтерфейси для типобезпеки.
- Маршрути
-
Описують ендпоінти (CRUD).
-
Типізують Request, Response.
-
Використовують middleware для аутентифікації, валідації.
- Валідація та типізація
-
zod, class-validator або joi для перевірки вхідних даних.
-
Інтерфейси/типи для моделей.
- Документація
- Swagger/OpenAPI для автогенерації документації.
- Приклад мінімального 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?
- Інтерфейси та типи
-
Використовуються для опису доменних моделей (User, Product, Order).
-
Забезпечують статичну перевірку відповідності даних.
- Класи / DTO
-
Використовуються для передачі даних між шарами (DTO: Data Transfer Object).
-
Можуть мати валідацію (наприклад, через class-validator).
- ORM/ODM інтеграція
-
З TypeScript добре працюють:
-
TypeORM / Prisma (SQL)
-
Mongoose (MongoDB)
-
Вони генерують типи моделей з бази або навпаки — з TS-кодів.
- Валідація + серіалізація
-
zod, yup, class-validator для перевірки схем.
-
Це дає гарантію, що дані з клієнта відповідають моделі.
- Приклад моделі (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 і як ними керувати?
- Рівні взаємодії
-
SQL-запити напряму → pg, mysql2, mssql драйвери. Гнучко, але більше boilerplate.
-
Query builders → Knex.js, Kysely. Дає типобезпеку і менш громіздкі запити.
-
ORM/ODM → Prisma, TypeORM, Sequelize, Mongoose (для MongoDB). Автоматично генерують моделі, типи, відношення.
- Організація коду
-
Створюють шар repository для роботи з БД.
-
Сервіси звертаються до репозиторіїв, а не напряму до драйвера.
-
Використовують DTO/інтерфейси для повернення типізованих результатів.
- Міграції та схеми
-
Prisma/TypeORM мають вбудовані інструменти для міграцій.
-
Забезпечує контроль версій БД.
- Приклад (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?
- Автентифікація (Authentication)
-
JWT (JSON Web Token) → найпоширеніший варіант. Токен зберігається в
Authorization: Bearer <token>
. -
Session + Cookies → через
express-session
, часто з Redis. Підходить для веб-додатків. -
OAuth2 / OpenID Connect → інтеграція з Google, GitHub, Microsoft.
-
API Keys → для сервіс-ту-сервіс інтеграцій.
- Авторизація (Authorization)
-
Role-based (RBAC) → ролі (
admin
,user
,manager
). -
Permission-based (PBAC) → більш гранульований контроль доступу.
-
Attribute-based (ABAC) → правила на основі атрибутів користувача й ресурсу.
- Практики реалізації в TypeScript API
-
Middleware для перевірки токенів (наприклад,
express-jwt
,passport-jwt
). -
Використання DTO для типізації
req.user
. -
Зберігання секретів у Vault/AWS Secrets Manager, а не в коді.
- Приклад (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)?
- Основні принципи DDD у TS
-
Domain layer: інтерфейси, value objects, entities → чисті TS-типи та класи.
-
Application layer: сервіси/юзкейси, які координують доменну логіку.
-
Infrastructure layer: робота з БД, API, сторонні сервіси (реалізації інтерфейсів).
-
Presentation layer: контролери/REST/GraphQL, які викликають application layer.
- Типобезпека як перевага
-
Value objects описуються через класи або типи (наприклад, Email, Price).
-
Використання readonly і private захищає інваріанти.
-
DTO/інтерфейси забезпечують чітку межу між шарами.
- Приклад сутності (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;
}
}
- Repositories
- Визначаються як інтерфейси у домені:
export interface UserRepository {
findById(id: number): Promise<User | null>;
save(user: User): Promise<void>;
}
- Реалізація у infrastructure (ORM/DB).
- Application Services (Use Cases)
- Координують бізнес-логіку без знання деталей інфраструктури.
Головна ідея: TypeScript дає строгу типізацію, що ідеально лягає на концепцію DDD — чіткі доменні моделі, інваріанти, розділення відповідальностей.
85. Як реалізувати пошук подій у бекенді на TypeScript?
- Моделювання сутності "Подія" (Event)
- Використати інтерфейс або ORM-модель:
interface Event {
id: number;
title: string;
date: Date;
location: string;
tags?: string[];
}
- Джерело даних
-
Якщо база SQL → Prisma/TypeORM (WHERE title ILIKE %...%).
-
Якщо NoSQL (MongoDB) → фільтрація через find({ title: /regex/ }).
- 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);
}
- Покращення пошуку
-
Індекси в БД (title, date).
-
Повнотекстовий пошук (Postgres tsvector, Elasticsearch, Meilisearch).
-
Пагінація (skip, take або LIMIT, OFFSET).
Основна ідея: в TypeScript пошук подій — це типізована модель + query-логіка (ORM/DB) + REST/GraphQL endpoint.
86. Що таке ізоморфні (універсальні) додатки в контексті TypeScript і як вони працюють?
- Визначення
-
Ізоморфний (універсальний) додаток — це застосунок, де один і той самий код (зазвичай написаний на TypeScript/JavaScript) виконується і на сервері, і на клієнті.
-
Найчастіше йдеться про рендеринг UI: сервер віддає HTML, а клієнт "гідратує" його і підхоплює подальшу логіку.
- Технологічна основа
-
React + TypeScript → Next.js — типовий приклад ізоморфного фреймворку.
-
Код компонентів пишеться на TS, використовується і для SSR (server-side rendering), і для CSR (client-side rendering).
- Переваги
-
Кращий SEO завдяки SSR.
-
Швидший TTFB (користувач одразу бачить контент).
-
Повторне використання типів/бізнес-логіки між фронтендом і бекендом.
- Приклад ізоморфного підходу (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?
- Суперсет JavaScript
-
TypeScript — це надбудова над JS, тому він підтримує всі сучасні можливості ECMAScript (ES6+).
-
Ви можете писати async/await, destructuring, classes, modules, optional chaining, nullish coalescing тощо.
- Компільований код
-
TS транслює сучасний синтаксис ES у код, сумісний з цільовою версією JS (target у tsconfig.json), наприклад:
-
target: "ES5" → транскомпіляція для старих браузерів.
-
target: "ES2022" → мінімальна трансформація, сучасні фічі залишаються.
- Типізація та нові фічі
-
TypeScript додає статичну типізацію, enums, interfaces, generics, tuples — чого немає в ES.
-
Нові TS-фічі часто випереджають офіційний стандарт ECMAScript, наприклад ??=, ??, as const.
- Приклад сучасного коду 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 фічі.
- Висновок
- TypeScript повністю сумісний з останніми стандартами ECMAScript і дає додаткові можливості для безпечної типізації, які не передбачені у JS.
88. Які додаткові можливості TypeScript пропонує поверх ES6+ стандартів?
- Статична типізація
-
TypeScript вводить type, interface, enum, tuple, readonly, generics.
-
Дозволяє перевіряти типи на етапі компіляції, чого немає в ES6.
- Покращена робота з класами
-
Підтримка модифікаторів доступу: public, private, protected.
-
abstract класи та методи.
-
Підтримка інтерфейсів для контрактів класів.
- Розширені конструкції функцій
-
Типізація параметрів і поверненого значення функцій.
-
Optional та default параметри з типовою перевіркою.
- Сучасний синтаксис ES6+
-
async/await, destructuring, spread/rest, modules, optional chaining ?., nullish coalescing ??.
-
TS повністю підтримує ці фічі і може транслювати їх під старіші таргети (ES5, ES2015).
- Краща інтеграція з IDE та автодоповнення
- Завдяки типізації, TS забезпечує точне IntelliSense та рефакторинг.
- Приклад:
interface User {
id: number;
name: string;
}
const users: User[] = [];
const firstUserName = users[0]?.name ?? "Unknown"; // ES2020 фічі + типізація
Основна ідея: TypeScript додає типи, інтерфейси, generics та строгіший контроль, зберігаючи всі сучасні фічі ES6+ і забезпечуючи сумісність із старішими середовищами.
89. Як TypeScript підтримує та працює з асинхронними ітераторами?
- Визначення
-
Асинхронний ітератор (AsyncIterator) дозволяє ітерувати над даними, які надходять асинхронно (Promise, стріми, API-виклики).
-
Використовується разом із for await...of.
- Типізація в TypeScript
- Можна явно вказати тип елементів, що повертає асинхронний ітератор:
async function* fetchData(): AsyncGenerator<number> {
for (let i = 0; i < 3; i++) {
await new Promise(res => setTimeout(res, 100));
yield i;
}
}
- Використання for await...of
async function main() {
for await (const num of fetchData()) {
console.log(num);
}
}
main();
- TypeScript перевіряє, що fetchData() повертає AsyncIterable.
- Сумісність з ES2018+
-
Асинхронні ітератори були додані в ES2018.
-
TypeScript підтримує їх, трансформуючи для таргетів ES2018+ без додаткових бібліотек.
- Переваги в TS
-
Статична типізація елементів ітератора.
-
Повна інтеграція з Promise та async/await.
-
Можливість створювати потокові дані, не блокуючи основний потік.
90. Які стратегії оптимізації продуктивності застосовують у програмах на TypeScript?
- Оптимізація на рівні TypeScript/JavaScript
-
Використовувати типи і інтерфейси для запобігання зайвих перевірок у рантаймі.
-
Мінімізувати використання any, щоб уникнути неочікуваних конверсій.
-
Використовувати readonly, const для зменшення мутабельності.
- Асинхронність і конкурентність
-
Promise.all для паралельних запитів.
-
Асинхронні ітератори для потокових даних.
-
Дебаунс і троттл для обробки подій у UI.
- Оптимізація фронтенд-коду
-
Динамічний імпорт модулів (import()) → code splitting.
-
Tree-shaking у збірниках (Webpack, Vite).
-
Мінімізація об’єктів і масивів, уникання глибоких копій без потреби.
- Профілювання і кешування
-
Використовувати DevTools для виявлення "гарячих" функцій.
-
Кешування результатів складних обчислень (memoization).
-
Кешування API-запитів або локальне збереження даних (IndexedDB, LocalStorage).
- Оптимізація Node.js бекенду
-
Використовувати потокову обробку великих даних (streams).
-
Уникати блокуючого коду і синхронних операцій.
-
Підвищити продуктивність через правильний розподіл процесів (cluster, worker_threads).
- Приклад 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-проєктах?
- Визначення Tree Shaking
-
Tree shaking — це процес видалення невикористаного коду під час збірки.
-
Дозволяє зменшити розмір бандла, залишаючи тільки імпортовані функції, класи або константи.
- Підтримка у TypeScript
-
TypeScript компілює код у ES-модулі (
import
/export
). -
Tree shaking працює лише з ESM, не з CommonJS.
- Налаштування у TS-проєкті
- У
tsconfig.json
:
{
"compilerOptions": {
"module": "ESNext",
"target": "ES6"
}
}
- Використовувати збирачі, які підтримують tree shaking: Webpack, Rollup, Vite.
- Приклад
// utils.ts
export function used() { console.log("Used"); }
export function unused() { console.log("Unused"); }
// main.ts
import { used } from "./utils";
used();
- Під час збірки
unused()
буде видалено, якщо включено tree shaking.
- Ключові поради
-
Використовувати іменовані експорти замість
export default
. -
Мінімізувати side effects у файлах (
"sideEffects": false
уpackage.json
). -
Стежити, щоб всі залежності підтримували ESM.
Основна ідея: TypeScript + ES-модулі дозволяють збирачу видаляти невикористаний код автоматично, зменшуючи розмір бандла і покращуючи продуктивність.
92. Як зменшити розмір фінального бандла в TypeScript-проєкті?
- Tree Shaking
-
Використовувати іменовані експорти замість
export default
. -
Переконатися, що всі файли ESM-модулі (
"module": "ESNext"
уtsconfig.json
). -
Вказати
"sideEffects": false
уpackage.json
.
- Code Splitting / Dynamic Imports
- Розбивати великий код на чанки за допомогою
import()
для відкладеного завантаження.
const module = await import("./heavyModule");
module.run();
- Мінімізація і компресія
-
Використовувати Terser / esbuild / SWC для мінімізації JS-коду.
-
Gzip або Brotli для продакшн-деплойменту.
- Видалення неактивного коду та поліморфів
-
Уникати великих утиліт або бібліотек без конкретного імпорту.
-
Імпортувати тільки потрібні функції (
import { debounce } from "lodash"
замістьimport _ from "lodash"
).
- Оптимізація типів
-
Типи TS не потрапляють у бандл → використовуй їх для розробки, не додаючи runtime-код.
-
Використовувати
import type
для імпорту типів, щоб не збільшувати код.
- Приклад import type:
import type { User } from "./types";
function greet(user: User) {
console.log(`Hello, ${user.name}`);
}
- TS видаляє типи з компільованого JS.
Основна ідея: комбінування tree shaking, динамічних імпортів, мінімізації та правильного використання типів TS дозволяє суттєво зменшити розмір пакета.
93. Які нові можливості з'явилися в останній версії TypeScript?
- Покращення продуктивності
- Оптимізовано структуру коду та алгоритми, що призвело до зменшення часу компіляції та обсягу пам'яті, необхідного для роботи TypeScript .
- Покращення роботи з ECMAScript модулями (ESM)
- Додано нові можливості для кращої підтримки ESM у Node.js та бандлерах .
- Покращення роботи з декораторами
- Реалізація нового стандарту декораторів, що дозволяє краще налаштовувати класи та їхні члени у повторно використовуваному вигляді .
- Нова можливість для авторів бібліотек
- Додано нові способи для авторів бібліотек контролювати виведення типів для узагальнених параметрів .
- Розширена підтримка JSDoc
- Покращено функціональність JSDoc для кращої інтеграції з TypeScript .
- Спрощення конфігурації
- Спрощено налаштування TypeScript для зручнішого використання .
-
Зменшення розміру пакета: TypeScript 5.0 містить значні зміни в структурі коду, що дозволяє зменшити розмір пакета .
-
Покращення швидкості: Оптимізовано алгоритми, що призвело до покращення швидкості компіляції .
- Покращення повідомлень про помилки: TypeScript 5.0 зосереджено на покращенні досвіду розробника через більш детальні та корисні повідомлення про помилки .
94. Як застосовувати шаблонні літеральні типи (template literal types) у TypeScript?
- Визначення
-
Шаблонні літеральні типи дозволяють комбінувати рядкові літерали та типи для створення нових типів.
-
Синтаксис схожий на template strings у JavaScript, але працює на рівні типів.
- Приклад базового використання
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"
- Використання з функціями
function setTheme(theme: ColoredShade) {
console.log(`Theme set to ${theme}`);
}
setTheme("light-red"); // ✅ коректно
setTheme("dark-yellow"); // ❌ помилка компіляції
- Комбінація з умовними типами
type Prefix = "on" | "off";
type Event = "Click" | "Hover";
type EventHandler = `${Prefix}${Event}`;
// "onClick" | "onHover" | "offClick" | "offHover"
- Переваги
-
Створення типобезпечних рядкових констант без дублювання.
-
Легко підтримувати конвенції іменування, напр. для CSS-класів або API-параметрів.
-
Інтегрується з union типами та генеріками.
Основна ідея: шаблонні літеральні типи дають змогу створювати складні рядкові типи на основі простих, забезпечуючи сувору типізацію і зменшуючи помилки.
95. Які покращення TypeScript внесла у вивід типів (type inference) для умовних типів?
- Визначення
-
Умовні типи (
T extends U ? X : Y
) дозволяють повертати різні типи залежно від вхідного. -
TypeScript тепер краще виводить типи при складних комбінаціях, особливо з generic та union типами.
- Покращений інференс для 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.
- Умовні типи з 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
- Контекстна типізація для generics
- TypeScript краще передбачає типи при передачі generic параметрів у функції.
function wrap<T>(value: T) {
return { value };
}
const wrapped = wrap(42); // { value: number } без явного вказання <number>
- Переваги
-
Менше явних анотацій типів.
-
Покращене автодоповнення і попередження помилок.
-
Складні типи (union, intersection, mapped types) стають більш передбачуваними.
Основна ідея: TypeScript зараз точніше виводить типи для умовних конструкцій, generics та infer, зменшуючи потребу у ручних анотаціях і роблячи код більш безпечним.
96. Для чого потрібні позначені елементи (labeled tuples) у TypeScript?
- Визначення
Позначені кортежі — це кортежі, де кожен елемент має ім’я, що покращує читаність і автодоповнення.
Синтаксис:
type Point = [x: number, y: number];
const p: Point = [10, 20];
- Переваги
Зрозуміліший код: зрозуміло, що означає кожен елемент.
Автодоповнення в 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
-
TypeScript — відкритий проект на GitHub: https://github.com/microsoft/TypeScript
-
Можна:
-
фіксити баги (
bug reports
), -
додавати або покращувати типи,
-
пропонувати нові фічі (
feature requests
), -
робити PR з виправленнями.
-
- Покращення екосистеми
-
Створювати бібліотеки або декларації типів (
DefinitelyTyped
). -
Писати npm-пакети з TypeScript і надавати
.d.ts
файли. -
Вносити зміни у типи для популярних бібліотек (PR до DefinitelyTyped ).
- Документація та навчальні матеріали
-
Дописувати статті, гіди, блог-пости, туторіали.
-
Перекладати офіційну документацію TS.
-
Писати приклади та шаблони проектів для спільноти.
- Спільнота та підтримка
-
Відповідати на питання на StackOverflow, Discord, GitHub Discussions.
-
Організовувати або брати участь у локальних/онлайн митапах.
-
Робити огляди бібліотек, нових фіч TypeScript.
- Підтримка інструментів
-
Розробляти плагіни для IDE (VSCode), лінтерів, CLI-інструментів.
-
Тестувати нові версії TypeScript у власних проєктах і звітувати баги.
Основна ідея: внесок може бути як кодовий (PR, DefinitelyTyped), так і інформаційний (документація, навчання, спільнота), і будь-який такий внесок робить екосистему TypeScript сильнішою.
98. Яку роль відіграє TypeScript у проєктах з відкритим кодом?
- Покращена безпека коду
-
Статична типізація дозволяє уникати багатьох помилок ще на етапі компіляції.
-
У великих відкритих проєктах це критично для підтримки стабільності.
- Зрозумілі API та документація через типи
-
Типи слугують самодокументованим кодом.
-
У відкритих бібліотеках користувачі одразу бачать, які параметри і значення очікуються.
- Масштабованість
-
TypeScript полегшує роботу з великими кодовими базами та численними контриб’юторами.
-
Генеровані
.d.ts
файли дозволяють іншим мовам/середовищам (наприклад, JS) безпечно використовувати бібліотеку.
- Сумісність з існуючим JS-кодом
- TypeScript дозволяє поступово типізувати проєкти, що вже існують у JS, без переписування всього коду.
- Покращене співробітництво
-
Контриб’ютори легше розуміють структуру і контракти коду.
-
Зменшується кількість помилок у pull request.
- Приклади успішних open-source проєктів на TS
-
React, Vue 3, Angular, Deno, NestJS, RxJS — великі проєкти на TypeScript.
-
TypeScript допомагає підтримувати високу якість коду та стабільність API.
Основна ідея: TypeScript у відкритому коді підвищує безпеку, читаємість, масштабованість і полегшує співпрацю багатьох розробників, що критично для open-source екосистеми.
99. Як відстежувати зміни та дотримуватися найкращих практик у TypeScript?
- Офіційні джерела
-
TypeScript Blog: https://devblogs.microsoft.com/typescript — анонси нових версій, фічі, release notes.
-
Документація: https://www.typescriptlang.org/docs/ — актуальні гіди та приклади.
- Соціальні мережі та спільнота
-
Twitter акаунти розробників TS (наприклад, @dan_abramov, @andrewbranch).
-
GitHub Discussions, StackOverflow, Reddit (r/typescript).
- Підписка на оновлення
-
RSS/Email для TypeScript Blog.
-
YouTube-канали з туторіалами та оглядами нових фіч.
- Відкритий код та аналіз бібліотек
-
Стежити за популярними проєктами на TypeScript (React, NestJS, RxJS) для прикладів реального використання.
-
Аналізувати PR і release notes бібліотек, щоб бачити, як використовують TS у продакшн.
- Код-рев’ю та стиль коду
-
Використовувати ESLint, TSLint (deprecated, але TS ESLint плагін активний).
-
Дотримуватися офіційних рекомендацій TypeScript щодо strict mode, типів, generics і модулів.
- Практика та реальні проєкти
-
Постійно писати код на TypeScript, експериментувати з новими фічами (template literal types, conditional types, decorators).
-
Робити рефакторинг старого JS-коду у TS для закріплення знань.
Основна ідея: комбінувати офіційні ресурси, спільноту, практичний досвід і регулярний аналіз open-source проєктів, щоб залишатися в курсі змін і найкращих практик TypeScript.
100. Як розробнику JavaScript ефективно перейти на TypeScript?
- Розуміння основ TypeScript
-
Вивчити базові типи (
string
,number
,boolean
,any
,unknown
,void
,null
,undefined
). -
Розібратися з інтерфейсами (
interface
), типами (type
) та кортежами (tuple
). -
Ознайомитися з
generics
і умовними типами.
- Налаштування проєкту
-
Ініціалізувати TypeScript:
tsc --init
. -
Встановити TypeScript і типи для Node/React/Express:
npm install --save-dev typescript @types/node
- Налаштувати
tsconfig.json
(наприклад,"strict": true
,"module": "ESNext"
,"target": "ES6"
).
- Поступовий перехід
-
Почати з переіменування
.js
файлів у.ts
або.tsx
(для React). -
Використовувати
allowJs: true
уtsconfig
для сумісності з існуючим JS. -
Додавати типи поетапно (
any
для швидкого старту, потім уточнювати).
- Інтеграція з інструментами
-
ESLint з плагіном для TypeScript (
@typescript-eslint
). -
IDE з підтримкою TypeScript (VSCode) для автодоповнення та рефакторингу.
- Робота з бібліотеками
- Встановлювати типи для сторонніх пакетів:
npm install --save-dev @types/express
- Використовувати декларації типів, якщо бібліотека написана на JavaScript.
- Практика і рефакторинг
-
Писати невеликі модулі на TypeScript.
-
Постійно рефакторити старий JS-код, додаючи типи і generics.
-
Вчитися на open-source проєктах, написаних на TypeScript.
- Кращі практики
-
Використовувати строгий режим (
strict: true
). -
Використовувати
readonly
для незмінних структур. -
Віддавати перевагу типам над
any
. -
Використовувати інтерфейси для API контрактів і DTO.
Основна ідея: почати з базових типів, налаштувати проєкт, поступово типізувати існуючий JS-код і застосовувати кращі практики для підтримки стабільності та безпеки коду.