TypeScript шпаргалка — типи, інтерфейси, generics
Довідник з основ TypeScript для тих, хто вже знає JavaScript. Усі приклади — практичні, з реальними сценаріями. Для глибшого вивчення — уроки TypeScript. Для порівняння JS і TS — стаття TypeScript vs JavaScript.
Також дивись: JavaScript основи · JS масиви та методи
Базові типи
let name: string = "Олена";
let age: number = 25;
let isStudent: boolean = true;
let nothing: null = null;
let notDefined: undefined = undefined;
| Тип | Опис | Приклад |
|---|---|---|
string | Рядок | "hello", `шаблон` |
number | Число (int і float) | 42, 3.14 |
boolean | Логічний | true, false |
null | Явна відсутність | null |
undefined | Не визначено | undefined |
any | Будь-що (вимикає перевірку) | уникай! |
unknown | Будь-що (безпечний) | потребує перевірки перед використанням |
never | Ніколи не відбувається | функція, що завжди кидає помилку |
void | Нічого не повертає | функція без return |
any вимикає всю перевірку типів. Використовуй unknown замість any — він змусить перевірити тип перед використанням.
Type Inference — TypeScript вгадує типи
Не обов'язково писати тип скрізь — TS вміє виводити його автоматично:
let city = "Київ"; // TS знає: string
let count = 0; // TS знає: number
let items = ["a", "b"]; // TS знає: string[]
const API_URL = "https://api.example.com"; // TS знає: "https://api.example.com" (literal type)
Правило: якщо TS може вивести тип — не пиши його вручну. Анотуй явно параметри функцій і складні випадки.
Функції
Типізація параметрів і повернення
function add(a: number, b: number): number {
return a + b;
}
const greet = (name: string): string => {
return `Привіт, ${name}!`;
};
Optional і default параметри
function createUser(name: string, age?: number): string {
return age ? `${name}, ${age} років` : name;
}
function fetchData(url: string, timeout: number = 5000): void {
// ...
}
Callback-функції
function processItems(items: string[], callback: (item: string, index: number) => void): void {
items.forEach(callback);
}
Функція, що нічого не повертає vs яка ніколи не завершується
function logMessage(msg: string): void {
console.log(msg);
}
function throwError(msg: string): never {
throw new Error(msg);
}
Інтерфейси (interface)
Базовий інтерфейс
interface User {
id: number;
name: string;
email: string;
age?: number; // optional — може бути, може ні
readonly createdAt: Date; // не можна змінити після створення
}
const user: User = {
id: 1,
name: "Олена",
email: "olena@example.com",
createdAt: new Date(),
};
Розширення (extends)
interface Admin extends User {
role: "admin" | "superadmin";
permissions: string[];
}
Інтерфейс для функції
interface SearchFunction {
(query: string, limit?: number): Promise<string[]>;
}
Type Alias (type)
type ID = string | number;
type Status = "loading" | "success" | "error";
type Point = { x: number; y: number };
interface vs type — коли що
| Ситуація | Рекомендація |
|---|---|
| Об'єкт / клас | interface |
| Union, literal, tuple | type |
| Розширення (extends) | обидва працюють |
| Declaration merging | тільки interface |
// interface — розширюється через extends
interface Animal {
name: string;
}
interface Dog extends Animal {
breed: string;
}
// type — комбінується через &
type Animal2 = { name: string };
type Dog2 = Animal2 & { breed: string };
Union та Literal типи
Union — "або"
let id: string | number;
id = "abc"; // ✅
id = 123; // ✅
id = true; // ❌
Literal — конкретні значення
type Direction = "up" | "down" | "left" | "right";
type HttpStatus = 200 | 301 | 404 | 500;
function move(direction: Direction): void {
// direction може бути ТІЛЬКИ "up", "down", "left", "right"
}
Discriminated Unions — для моделювання станів
type ApiResponse =
| { status: "loading" }
| { status: "success"; data: string[] }
| { status: "error"; message: string };
function handle(response: ApiResponse) {
switch (response.status) {
case "loading":
console.log("Завантаження...");
break;
case "success":
console.log(response.data); // TS знає: тут є .data
break;
case "error":
console.log(response.message); // TS знає: тут є .message
break;
}
}
Масиви та кортежі
Масиви
const names: string[] = ["Олена", "Іван"];
const scores: Array<number> = [95, 87, 73];
const matrix: number[][] = [[1, 2], [3, 4]];
Кортежі (Tuple) — фіксована довжина і типи
const pair: [string, number] = ["вік", 25];
const rgb: [number, number, number] = [255, 128, 0];
// Деструктуризація
const [label, value] = pair; // label: string, value: number
Generics — узагальнені типи
Навіщо
Generics дозволяють писати код, який працює з будь-яким типом, зберігаючи перевірку:
// Без generics — втрачаємо тип
function first(arr: any[]): any {
return arr[0];
}
// З generics — тип зберігається
function first<T>(arr: T[]): T {
return arr[0];
}
first([1, 2, 3]); // повертає number
first(["a", "b", "c"]); // повертає string
Generic інтерфейс
interface ApiResponse<T> {
data: T;
status: number;
message: string;
}
type UserResponse = ApiResponse<User>;
type ProductResponse = ApiResponse<Product[]>;
Constraints — обмеження generic
function getLength<T extends { length: number }>(item: T): number {
return item.length;
}
getLength("hello"); // ✅ string має .length
getLength([1, 2, 3]); // ✅ масив має .length
getLength(42); // ❌ number не має .length
Utility Types — вбудовані трансформації
| Utility | Що робить | Приклад |
|---|---|---|
Partial<T> | Всі поля optional | Partial<User> — для форм оновлення |
Required<T> | Всі поля обов'язкові | Required<Config> |
Pick<T, K> | Обрати лише деякі поля | Pick<User, "id" | "name"> |
Omit<T, K> | Прибрати деякі поля | Omit<User, "password"> |
Record<K, V> | Об'єкт з ключами K і значеннями V | Record<string, number> |
Readonly<T> | Всі поля readonly | Readonly<Config> |
Приклади
interface User {
id: number;
name: string;
email: string;
password: string;
}
// Для форми оновлення — не всі поля обов'язкові
type UpdateUser = Partial<User>;
// Для відображення — без пароля
type PublicUser = Omit<User, "password">;
// Для списку — тільки id і name
type UserPreview = Pick<User, "id" | "name">;
// Словник з рядковими ключами
type Scores = Record<string, number>;
const exam: Scores = { math: 95, english: 87 };
Type Narrowing — звуження типів
TypeScript звужує типи автоматично після перевірок:
typeof
function format(value: string | number): string {
if (typeof value === "string") {
return value.toUpperCase(); // TS знає: string
}
return value.toFixed(2); // TS знає: number
}
instanceof
function handleError(error: Error | string): string {
if (error instanceof Error) {
return error.message; // TS знає: Error
}
return error; // TS знає: string
}
in
interface Cat { meow(): void }
interface Dog { bark(): void }
function speak(animal: Cat | Dog) {
if ("meow" in animal) {
animal.meow(); // TS знає: Cat
} else {
animal.bark(); // TS знає: Dog
}
}
Truthiness
function greet(name?: string) {
if (name) {
console.log(name.toUpperCase()); // TS знає: string (не undefined)
}
}
Enums — і чому їх краще уникати
TypeScript має enum — але це одна з найспірніших його фіч. На відміну від усього іншого в TS, enum генерує реальний JavaScript-код у рантаймі. Це напівтип-напіврантайм конструкція, і її поведінка не завжди очевидна.
Як виглядає enum
enum Role {
Admin = "ADMIN",
Editor = "EDITOR",
Viewer = "VIEWER",
}
Після компіляції це перетворюється на:
var Role;
(function (Role) {
Role["Admin"] = "ADMIN";
Role["Editor"] = "EDITOR";
Role["Viewer"] = "VIEWER";
})(Role || (Role = {}));
Це зайвий код, який потрапляє в бандл. А числові enum ще гірші — вони створюють зворотний маппінг (ключ → значення І значення → ключ), що часто плутає.
Рекомендація: уникай enum. Використовуй as const + typeof — це чистий TypeScript без рантайм-артефактів, з кращим tree-shaking і більш передбачуваною поведінкою.
Краща альтернатива: as const + typeof
as const перетворює значення в точні літеральні типи — без генерації зайвого JS:
// ✅ Замість enum — об'єкт з as const
const ROLES = {
Admin: "ADMIN",
Editor: "EDITOR",
Viewer: "VIEWER",
} as const;
type Role = (typeof ROLES)[keyof typeof ROLES]; // "ADMIN" | "EDITOR" | "VIEWER"
function checkAccess(role: Role): boolean {
return role === ROLES.Admin;
}
Після компіляції — просто об'єкт. Ніяких IIFE, ніяких зворотних маппінгів. Тип Role існує тільки на етапі компіляції і повністю зникає з бандла.
as const для масивів і конфігів
// Масив з точними типами
const STATUSES = ["draft", "published", "archived"] as const;
type Status = (typeof STATUSES)[number]; // "draft" | "published" | "archived"
// Конфіг з точними значеннями
const config = {
api: "https://api.example.com",
timeout: 5000,
retries: 3,
} as const;
// config.api має тип "https://api.example.com", а не string
// config.timeout має тип 5000, а не number
Простий випадок — union literal
Якщо не потрібен маппінг ключ → значення, найпростіший варіант:
type Direction = "up" | "down" | "left" | "right";
type HttpStatus = 200 | 301 | 404 | 500;
| Підхід | Рантайм-код | Tree-shaking | Зрозумілість |
|---|---|---|---|
enum | Так (IIFE) | Погано | Неочевидна поведінка |
as const + typeof | Тільки об'єкт | Добре | Прозоро |
| Union literal | Ні | Ідеально | Найпростіше |
Best Practices — як писати кращий TypeScript
Думай інтерфейсами, не змінними
Перш ніж писати функцію — опиши shape даних. Це змінює підхід до дизайну коду:
// ❌ Спочатку код, потім типи (JS-мислення)
function createOrder(userId, items, discount) { ... }
// ✅ Спочатку контракт, потім код (TS-мислення)
interface CreateOrderInput {
userId: string;
items: OrderItem[];
discount?: Discount;
}
function createOrder(input: CreateOrderInput): Order { ... }
Уникай type assertions (as) де можливо
as — це "повір мені, я знаю краще". Іноді потрібно, але частіше — ознака проблеми:
// ❌ Каже "це User" — але TS не перевіряє
const user = JSON.parse(data) as User;
// ✅ Перевіряє в рантаймі — TS звужує тип автоматично
function isUser(data: unknown): data is User {
return typeof data === "object" && data !== null && "id" in data && "name" in data;
}
const parsed = JSON.parse(data);
if (isUser(parsed)) {
console.log(parsed.name); // TS знає: це User
}
satisfies — перевір тип без втрати точності
type Theme = Record<string, string>;
// ❌ as Theme — втрачаємо точні ключі
const theme = { primary: "#007bff", secondary: "#6c757d" } as Theme;
// ✅ satisfies — перевіряє тип І зберігає точні ключі
const theme = {
primary: "#007bff",
secondary: "#6c757d",
} satisfies Theme;
theme.primary; // ✅ TS знає що це конкретно "primary", не просто string
theme.tertiary; // ❌ Property 'tertiary' does not exist
Використовуй strict mode
У tsconfig.json завжди вмикай "strict": true — це включає strictNullChecks, noImplicitAny та інші перевірки, які ловлять більшість помилок:
{
"compilerOptions": {
"strict": true
}
}
Типові помилки
1. Використання any замість конкретного типу
// ❌ Погано — any вимикає перевірку
function parse(data: any) { return data.name; }
// ✅ Добре — конкретний тип або unknown
function parse(data: unknown) {
if (typeof data === "object" && data !== null && "name" in data) {
return (data as { name: string }).name;
}
}
2. Зайві анотації де TS і так знає тип
// ❌ Зайве — TS виведе тип сам
const name: string = "Олена";
const items: number[] = [1, 2, 3];
// ✅ Достатньо
const name = "Олена";
const items = [1, 2, 3];
3. Забути обробити всі варіанти union
type Status = "active" | "inactive" | "banned";
// ❌ Забули "banned"
function getMessage(status: Status): string {
if (status === "active") return "Активний";
if (status === "inactive") return "Неактивний";
// TS не попередить без exhaustive check
}
// ✅ Exhaustive check з never
function getMessage(status: Status): string {
switch (status) {
case "active": return "Активний";
case "inactive": return "Неактивний";
case "banned": return "Заблокований";
default:
const _exhaustive: never = status; // ❌ якщо забув варіант
return _exhaustive;
}
}
4. Мутація readonly поля
interface Config {
readonly apiUrl: string;
}
const config: Config = { apiUrl: "https://api.example.com" };
config.apiUrl = "other"; // ❌ Cannot assign to 'apiUrl' because it is a read-only property
5. Плутати interface та type для union
// ❌ interface не може бути union
interface Status {
"loading" | "success" | "error" // Syntax Error
}
// ✅ Для union — тільки type
type Status = "loading" | "success" | "error";
Практика:
- TypeScript Playground — експериментуй з типами прямо в браузері
- Type Challenges — задачі на типи від простих до божевільних (44k+ зірок)
Документація:
- TypeScript Handbook — офіційна документація
- Офіційні шпаргалки TypeScript — візуальні PDF від команди TS
- TypeScript for JS Programmers — якщо хочеш швидкий вступ
React + TypeScript:
- typescript-cheatsheets/react — типізація компонентів, хуків, подій (47k+ зірок)
- React TypeScript Cheatsheet — сайт-довідник з прикладами
Matt Pocock (один з найкращих TS-авторів):
- Total TypeScript — безкоштовні курси та статті
- Matt Pocock YouTube — короткі відео про TS-патерни
Наш курс:
- Уроки TypeScript — 6 безкоштовних уроків від базових типів до generics
- TypeScript vs JavaScript — різниця — якщо ще не читав