TS дозволяє створювати гнучкі функції, класи та інтерфейси які працюють з будь якими типами, при цьому збрігаючи строго типізацію. Звучить як нісинітниця, але я зараз поясню.
Generic дозволяють створювати універсальні структури, які працюють з різними типами, не втрачаючи перевірку типів.
function identity<T>(value: T): T {
return value;
}
console.log(identity(5)); // ✅ 5 (TypeScript розуміє, що T = number)
console.log(identity("Hello")); // ✅ "Hello" (T = string)
T
– це загальний тип, який визначається під час виклику функції. TypeScript автоматично виводить тип T
із переданого значення. Ця функція працює для будь-якого типу (числа, рядка тощо).
Хоча TypeScript може сам вивести тип, його можна вказати явно:
console.log(identity<number>(42)); // ✅ 42
console.log(identity<string>("TypeScript")); // ✅ "TypeScript"
В класах це використовується плюс мінус так само, як і звичайна типізація:
class Box<T> {
private content: T;
constructor(value: T) {
this.content = value;
}
getContent(): T {
return this.content;
}
}
const numberBox = new Box<number>(10);
console.log(numberBox.getContent()); // ✅ 10
const stringBox = new Box<string>("Hello");
console.log(stringBox.getContent()); // ✅ "Hello"
Якщо пояснювати простим язиком, то генеріки, це функції, в яких ви передаєте тип як змінну, яку ви можете змінювати в залежності від функції. Сказано дуже спрощено, але по факту воно десь так і є.
Якщо вам потрібно обмежети типи, які може приймати generic, то це можна зробити ось так:
function printLength<T extends { length: number }>(item: T): void {
console.log(`Довжина: ${item.length}`);
}
printLength("Hello"); // ✅ Довжина: 5
printLength([1, 2, 3]); // ✅ Довжина: 3
printLength(10); // ❌ TypeScript має видати помилку тут!
Щоб дозволити використовуйте наступний синтаксис:
function double<T extends number | string>(value: T): T {
return (typeof value === "number" ? (value * 2) : (value + value)) as T;
}
console.log(double(10)); // ✅ 20
console.log(double("Hi")); // ✅ "HiHi"
// console.log(double(true)); // ❌ Помилка! Boolean не дозволений
Generic в інтерфейсах
Хочу щоб ви відразу звернули увагу, що параметрів generic може бути декілька, і не тільки в інтерфейсах. Ось так можна зробити інтерфейси:
interface Pair<T, U> {
first: T;
second: U;
}
const numberPair: Pair<number, string> = { first: 10, second: "ten" };
console.log(numberPair); // ✅ { first: 10, second: "ten" }
В extends також можна використовувати декілька парметрів, наприкладі keyof:
function getProperty<T, K extends keyof T>(obj: T, key: K) {
return obj[key];
}
const user = { name: "Олексій", age: 30 };
console.log(getProperty(user, "name")); // ✅ "Олексій"
console.log(getProperty(user, "age")); // ✅ 30
// console.log(getProperty(user, "email")); // ❌ Помилка! "email" не існує в `user`
Код виглядає трохи більш складним, але якщо ви читали попередні уроки, то тут все просто. Якщо не розумієте рекомендую перечитати урок про інтерфейси, посилання під уроком.
Динамічні об’єкти через Record
Record<K, V>
– це вбудований тип для створення дженеричних об’єктів.
type UserRoles = Record<string, boolean>;
const roles: UserRoles = {
admin: true,
editor: false,
};
console.log(roles.admin); // ✅ true
Record<K, V>
створює об’єкт, де всі ключі мають один тип V. kv, це якщо шо key value.
На цьому все, зустрінемос в наступному уроці.